#if FISHNET_THREADED_TICKSMOOTHERS 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.ScheduleUpdateRealtimeInterpolation()"); private static readonly ProfilerMarker _pm_ScheduleDiscardExcessiveTransformPropertiesQueue = new("TickSmoothingManager.ScheduleDiscardExcessiveTransformPropertiesQueue()"); private static readonly ProfilerMarker _pm_ScheduleSetMoveRates = new("TickSmoothingManager.ScheduleSetMoveRates()"); private static readonly ProfilerMarker _pm_ScheduleSetMovementMultiplier = new("TickSmoothingManager.ScheduleSetMovementMultiplier()"); private static readonly ProfilerMarker _pm_ScheduleAddTransformProperties = new("TickSmoothingManager.ScheduleAddTransformProperties()"); private static readonly ProfilerMarker _pm_ScheduleClearTransformPropertiesQueue = new("TickSmoothingManager.ScheduleClearTransformPropertiesQueue()"); private static readonly ProfilerMarker _pm_ScheduleModifyTransformProperties = new("TickSmoothingManager.ScheduleModifyTransformProperties()"); private static readonly ProfilerMarker _pm_ScheduleSnapNonSmoothedProperties = new("TickSmoothingManager.ScheduleSnapNonSmoothedProperties()"); private static readonly ProfilerMarker _pm_ScheduleTeleport = new("TickSmoothingManager.ScheduleTeleport()"); private static readonly ProfilerMarker _pm_Register = new("TickSmoothingManager.Register()"); private static readonly ProfilerMarker _pm_Unregister = new("TickSmoothingManager.Unregister()"); #endregion #region Const. /// /// Maximum allowed entries. /// private const int MAXIMUM_QUEUED = 256; /// /// Maximum allowed entries to be queued over the interpolation amount. /// private const int REQUIRED_QUEUED_OVER_INTERPOLATION = 3; #endregion /// /// NetworkManager on the same object as this script. /// private NetworkManager _networkManager; /// /// TimeManager on the same object as this script. /// private TimeManager _timeManager; /// /// PredictionManager on the same object as this script. /// private PredictionManager _predictionManager; /// /// TrackerTransformsPool. /// private readonly Stack _trackerTransformsPool = new(); /// /// TrackerTransformsPoolHolder. /// private Transform _trackerTransformsPoolHolder; /// /// TickSmootherController to index lookup. /// private readonly Dictionary _lookup = new(); /// /// Index to TickSmootherController and InitializationSettings lookup. /// private readonly List _indexToSmoother = new(); /// /// Index to TickSmootherController and NetworkBehaviours lookup. /// private readonly List _indexToNetworkBehaviour = new(); /// /// Index to TickSmootherController and redictionNetworkTransform lookup. /// private readonly List _indexToPredictionNetworkTransform = new(); /// /// Index to MoveRate lookup. /// How quickly to move towards goal values. /// private NativeList _moveRates; /// /// Index to Owner MovementSettings lookup. /// Settings to use for owners. /// private NativeList _ownerSettings; /// /// Index to Spectator MovementSettings lookup. /// Settings to use for spectators. /// private NativeList _spectatorSettings; /// /// Index to PreTickedMask lookup. /// True if a pretick occurred since last postTick. /// private NativeList _preTickedMask; /// /// Index to MoveImmediatelyMask lookup. /// True to begin moving soon as movement data becomes available. Movement will ease in until at interpolation value. False to prevent movement until movement data count meet interpolation. /// private NativeList _moveImmediatelyMask; /// /// Index to CanSmoothMask lookup. /// Returns if prediction can be used on this rigidbody. /// private NativeList _canSmoothMask; /// /// Index to UseOwnerSettingsMask lookup. /// True if to smooth using owner settings, false for spectator settings. /// This is only used for performance gains. /// private NativeList _useOwnerSettingsMask; /// /// Index to ObjectReconcilingMask lookup. /// private NativeList _objectReconcilingMask; /// /// Index to DetachOnStartMask lookup. /// True if to detach on smoothing start. /// private NativeList _detachOnStartMask; /// /// Index to AttachOnStopMask lookup. /// True if to attach on smoothing stop. /// private NativeList _attachOnStopMask; /// /// Index to IsMoving lookup. /// True if moving has started and has not been stopped. /// private NativeList _isMoving; /// /// Index to TeleportedTick lookup. /// Last tick this was teleported on. /// private NativeList _teleportedTick; /// /// Index to RealTimeInterpolation lookup. /// Current interpolation value, be it a flat value or adaptive. /// private NativeList _realTimeInterpolations; /// /// Index to MovementMultiplier lookup. /// Value to multiply movement by. This is used to reduce or increase the rate the movement buffer is consumed. /// private NativeList _movementMultipliers; /// /// Index to TransformProperties lookup. /// TransformProperties to move towards /// private StripedRingQueue _transformProperties; /// /// Index to PreTick Graphic TransformProperties Snapshot lookup. /// World values of the graphical after it's been aligned to initialized values in PreTick. /// private NativeList _preTickGraphicSnapshot; /// /// Index to PreTick Tracker TransformProperties Snapshot lookup. /// World values of the graphical after it's been aligned to initialized values in PreTick. /// private NativeList _postTickTrackerSnapshot; /// /// Index to Temp Target TransformProperties Snapshot lookup. /// private NativeList _tempTargetSnapshot; /// /// Index to OutSnapGraphicWorld lookup. /// private NativeList _outSnapGraphicWorld; /// /// Index to OutEnqueueTrackerWorld lookup. /// private NativeList _outEnqueueTrackerWorld; /// /// Index to QueuedTrackerProperties lookup. /// Properties for the tracker which are queued to be set when the tracker is setup. /// private NativeList _queuedTrackerProperties; /// /// Index to MoveToTargetPayloads lookup. /// private NativeList _moveToTargetPayloads; /// /// Index to UpdateRealtimeInterpolationPayloads lookup. /// private NativeList _updateRealtimeInterpolationPayloads; /// /// Index to DiscardExcessiveTransformPropertiesQueuePayloads lookup. /// private NativeList _discardExcessivePayloads; /// /// Index to SetMoveRatesPayloads lookup. /// private NativeList _setMoveRatesPayloads; /// /// Index to SetMovementMultiplierPayloads lookup. /// private NativeList _setMovementMultiplierPayloads; /// /// Index to AddTransformPropertiesPayloads lookup. /// private NativeList _addTransformPropertiesPayloads; /// /// Index to ClearTransformPropertiesQueuePayloads lookup. /// private NativeList _clearTransformPropertiesQueuePayloads; /// /// Index to ModifyTransformPropertiesPayloads lookup. /// private NativeList _modifyTransformPropertiesPayloads; /// /// Index to SnapNonSmoothedPropertiesPayloads lookup. /// private NativeList _snapNonSmoothedPropertiesPayloads; /// /// Index to TeleportPayloads lookup. /// private NativeList _teleportPayloads; /// /// Target objects TransformAccessArray. /// Transform the graphics should follow. /// private TransformAccessArray _targetTaa; /// /// Graphical objects TransformAccessArray. /// Cached value of the object to smooth. /// private TransformAccessArray _graphicalTaa; /// /// Tracker objects TransformAccessArray. /// Empty gameObject containing a transform which has properties checked after each simulation. /// If the graphical starts off as nested of targetTransform then this object is created where the graphical object is. /// Otherwise, this object is placed directly beneath targetTransform. /// private TransformAccessArray _trackerTaa; /// /// Subscription to callbacks state. /// private bool _subscribed; #endregion /// /// Initialize once from NetworkManager (pattern mirrors RollbackManager). /// internal void InitializeOnce_Internal(NetworkManager manager) { _networkManager = manager; _timeManager = manager.TimeManager; _predictionManager = manager.PredictionManager; if (!_trackerTransformsPoolHolder) { _trackerTransformsPoolHolder = new GameObject("Tracker Transforms Pool Holder").transform; DontDestroyOnLoad(_trackerTransformsPoolHolder.gameObject); } if (!_moveRates.IsCreated) _moveRates = new NativeList(64, Allocator.Persistent); if (!_ownerSettings.IsCreated) _ownerSettings = new NativeList(64, Allocator.Persistent); if (!_spectatorSettings.IsCreated) _spectatorSettings = new NativeList(64, Allocator.Persistent); if (!_preTickedMask.IsCreated) _preTickedMask = new NativeList(64, Allocator.Persistent); if (!_canSmoothMask.IsCreated) _canSmoothMask = new NativeList(64, Allocator.Persistent); if (!_useOwnerSettingsMask.IsCreated) _useOwnerSettingsMask = new NativeList(64, Allocator.Persistent); if (!_objectReconcilingMask.IsCreated) _objectReconcilingMask = new NativeList(64, Allocator.Persistent); if (!_detachOnStartMask.IsCreated) _detachOnStartMask = new NativeList(64, Allocator.Persistent); if (!_attachOnStopMask.IsCreated) _attachOnStopMask = new NativeList(64, Allocator.Persistent); if (!_moveImmediatelyMask.IsCreated) _moveImmediatelyMask = new NativeList(64, Allocator.Persistent); if (!_isMoving.IsCreated) _isMoving = new NativeList(64, Allocator.Persistent); if (!_teleportedTick.IsCreated) _teleportedTick = new NativeList(64, Allocator.Persistent); if (!_realTimeInterpolations.IsCreated) _realTimeInterpolations = new NativeList(64, Allocator.Persistent); if (!_movementMultipliers.IsCreated) _movementMultipliers = new NativeList(64, Allocator.Persistent); if (!_transformProperties.IsCreated) _transformProperties = new StripedRingQueue(64, MAXIMUM_QUEUED, Allocator.Persistent); if (!_preTickGraphicSnapshot.IsCreated) _preTickGraphicSnapshot = new NativeList(64, Allocator.Persistent); if (!_postTickTrackerSnapshot.IsCreated) _postTickTrackerSnapshot = new NativeList(64, Allocator.Persistent); if (!_tempTargetSnapshot.IsCreated) _tempTargetSnapshot = new NativeList(64, Allocator.Persistent); if (!_outSnapGraphicWorld.IsCreated) _outSnapGraphicWorld = new NativeList(64, Allocator.Persistent); if (!_outEnqueueTrackerWorld.IsCreated) _outEnqueueTrackerWorld = new NativeList(64, Allocator.Persistent); if (!_queuedTrackerProperties.IsCreated) _queuedTrackerProperties = new NativeList(64, Allocator.Persistent); if (!_moveToTargetPayloads.IsCreated) _moveToTargetPayloads = new NativeList(64, Allocator.Persistent); if (!_updateRealtimeInterpolationPayloads.IsCreated) _updateRealtimeInterpolationPayloads = new NativeList(64, Allocator.Persistent); if (!_discardExcessivePayloads.IsCreated) _discardExcessivePayloads = new NativeList(64, Allocator.Persistent); if (!_setMoveRatesPayloads.IsCreated) _setMoveRatesPayloads = new NativeList(64, Allocator.Persistent); if (!_setMovementMultiplierPayloads.IsCreated) _setMovementMultiplierPayloads = new NativeList(64, Allocator.Persistent); if (!_addTransformPropertiesPayloads.IsCreated) _addTransformPropertiesPayloads = new NativeList(64, Allocator.Persistent); if (!_clearTransformPropertiesQueuePayloads.IsCreated) _clearTransformPropertiesQueuePayloads = new NativeList(64, Allocator.Persistent); if (!_modifyTransformPropertiesPayloads.IsCreated) _modifyTransformPropertiesPayloads = new NativeList(64, Allocator.Persistent); if (!_snapNonSmoothedPropertiesPayloads.IsCreated) _snapNonSmoothedPropertiesPayloads = new NativeList(64, Allocator.Persistent); if (!_teleportPayloads.IsCreated) _teleportPayloads = new NativeList(64, Allocator.Persistent); if (!_targetTaa.isCreated) _targetTaa = new TransformAccessArray(64); if (!_graphicalTaa.isCreated) _graphicalTaa = new TransformAccessArray(64); if (!_trackerTaa.isCreated) _trackerTaa = new TransformAccessArray(64); // Subscribe to client connection state to (un)hook timing/prediction. _networkManager.ClientManager.OnClientConnectionState += ClientManager_OnClientConnectionState; if (_networkManager.ClientManager.Started) ChangeSubscriptions(true); } private void OnDestroy() { ChangeSubscriptions(false); if (_networkManager != null) { _networkManager.ClientManager.OnClientConnectionState -= ClientManager_OnClientConnectionState; } while (_trackerTransformsPool.TryPop(out Transform trackerTransform)) if (trackerTransform && trackerTransform.gameObject) Destroy(trackerTransform.gameObject); for (int i = 0; i < _indexToSmoother.Count; i++) { Transform trackerTransform = _trackerTaa[i]; if (trackerTransform && trackerTransform.gameObject) Destroy(trackerTransform.gameObject); } if (_moveRates.IsCreated) _moveRates.Dispose(); if (_ownerSettings.IsCreated) _ownerSettings.Dispose(); if (_spectatorSettings.IsCreated) _spectatorSettings.Dispose(); if (_preTickedMask.IsCreated) _preTickedMask.Dispose(); if (_canSmoothMask.IsCreated) _canSmoothMask.Dispose(); if (_useOwnerSettingsMask.IsCreated) _useOwnerSettingsMask.Dispose(); if (_objectReconcilingMask.IsCreated) _objectReconcilingMask.Dispose(); if (_detachOnStartMask.IsCreated) _detachOnStartMask.Dispose(); if (_attachOnStopMask.IsCreated) _attachOnStopMask.Dispose(); if (_moveImmediatelyMask.IsCreated) _moveImmediatelyMask.Dispose(); if (_isMoving.IsCreated) _isMoving.Dispose(); if (_teleportedTick.IsCreated) _teleportedTick.Dispose(); if (_realTimeInterpolations.IsCreated) _realTimeInterpolations.Dispose(); if (_movementMultipliers.IsCreated) _movementMultipliers.Dispose(); if (_transformProperties.IsCreated) _transformProperties.Dispose(); if (_preTickGraphicSnapshot.IsCreated) _preTickGraphicSnapshot.Dispose(); if (_postTickTrackerSnapshot.IsCreated) _postTickTrackerSnapshot.Dispose(); if (_tempTargetSnapshot.IsCreated) _tempTargetSnapshot.Dispose(); if (_outSnapGraphicWorld.IsCreated) _outSnapGraphicWorld.Dispose(); if (_outEnqueueTrackerWorld.IsCreated) _outEnqueueTrackerWorld.Dispose(); if (_queuedTrackerProperties.IsCreated) _queuedTrackerProperties.Dispose(); if (_moveToTargetPayloads.IsCreated) _moveToTargetPayloads.Dispose(); if (_updateRealtimeInterpolationPayloads.IsCreated) _updateRealtimeInterpolationPayloads.Dispose(); if (_discardExcessivePayloads.IsCreated) _discardExcessivePayloads.Dispose(); if (_setMoveRatesPayloads.IsCreated) _setMoveRatesPayloads.Dispose(); if (_setMovementMultiplierPayloads.IsCreated) _setMovementMultiplierPayloads.Dispose(); if (_addTransformPropertiesPayloads.IsCreated) _addTransformPropertiesPayloads.Dispose(); if (_clearTransformPropertiesQueuePayloads.IsCreated) _clearTransformPropertiesQueuePayloads.Dispose(); if (_modifyTransformPropertiesPayloads.IsCreated) _modifyTransformPropertiesPayloads.Dispose(); if (_snapNonSmoothedPropertiesPayloads.IsCreated) _snapNonSmoothedPropertiesPayloads.Dispose(); if (_teleportPayloads.IsCreated) _teleportPayloads.Dispose(); if (_targetTaa.isCreated) _targetTaa.Dispose(); if (_graphicalTaa.isCreated) _graphicalTaa.Dispose(); if (_trackerTaa.isCreated) _trackerTaa.Dispose(); _indexToNetworkBehaviour.Clear(); _indexToPredictionNetworkTransform.Clear(); _indexToSmoother.Clear(); _lookup.Clear(); _networkManager = null; _timeManager = null; _predictionManager = null; } /// /// Register a TickSmootherController with associated settings. /// public void Register(TickSmootherController smoother, InitializationSettings initializationSettings, MovementSettings ownerSettings, MovementSettings spectatorSettings) { using (_pm_Register.Auto()) { if (smoother == null) return; if (!TransformsAreValid(initializationSettings.GraphicalTransform, initializationSettings.TargetTransform)) return; /* Unset scale smoothing if not detaching. This is to prevent * the scale from changing with the parent if nested, as that * would result in the scale being modified twice, once on the parent * and once on the graphical. Thanks deo_wh for find! */ if (!initializationSettings.DetachOnStart) { ownerSettings.SmoothedProperties &= ~TransformPropertiesFlag.Scale; spectatorSettings.SmoothedProperties &= ~TransformPropertiesFlag.Scale; } if (_lookup.TryGetValue(smoother, out int index)) { _ownerSettings[index] = ownerSettings; _spectatorSettings[index] = spectatorSettings; return; } index = _indexToSmoother.Count; _lookup[smoother] = index; _indexToSmoother.Add(smoother); _indexToNetworkBehaviour.Add(initializationSettings.InitializingNetworkBehaviour); _indexToPredictionNetworkTransform.Add( initializationSettings.FavorPredictionNetworkTransform && initializationSettings.InitializingNetworkBehaviour != null && initializationSettings.InitializingNetworkBehaviour.NetworkObject != null && initializationSettings.InitializingNetworkBehaviour.NetworkObject.IsRigidbodyPredictionType ? initializationSettings.InitializingNetworkBehaviour.NetworkObject.PredictionNetworkTransform : null ); _moveRates.Add(new MoveRates(MoveRates.UNSET_VALUE)); _ownerSettings.Add(ownerSettings); _spectatorSettings.Add(spectatorSettings); _preTickedMask.Add(0); _canSmoothMask.Add( (byte)(initializationSettings.GraphicalTransform != null && _networkManager.IsClientStarted ? 1 : 0)); _useOwnerSettingsMask.Add( (byte)(initializationSettings.InitializingNetworkBehaviour == null || initializationSettings.InitializingNetworkBehaviour.IsOwner || !initializationSettings.InitializingNetworkBehaviour.Owner.IsValid ? 1 : 0)); _objectReconcilingMask.Add( (byte)(initializationSettings.InitializingNetworkBehaviour == null || initializationSettings.InitializingNetworkBehaviour.NetworkObject == null || initializationSettings.InitializingNetworkBehaviour.NetworkObject.IsObjectReconciling ? 1 : 0)); _detachOnStartMask.Add( (byte)(initializationSettings.DetachOnStart ? 1 : 0)); _attachOnStopMask.Add( (byte)(initializationSettings.AttachOnStop ? 1 : 0)); _moveImmediatelyMask.Add( (byte)(initializationSettings.MoveImmediately ? 1 : 0)); _isMoving.Add(default); _teleportedTick.Add(TimeManager.UNSET_TICK); _realTimeInterpolations.Add(default); _movementMultipliers.Add(default); _transformProperties.AddQueue(); _preTickGraphicSnapshot.Add(default); _postTickTrackerSnapshot.Add(default); _tempTargetSnapshot.Add(default); _outSnapGraphicWorld.Add(default); _outEnqueueTrackerWorld.Add(default); _queuedTrackerProperties.Add(new NullableTransformProperties(false, default)); _moveToTargetPayloads.Add(new MoveToTargetPayload(0, default)); _updateRealtimeInterpolationPayloads.Add(new UpdateRealtimeInterpolationPayload(0)); _discardExcessivePayloads.Add(new DiscardExcessiveTransformPropertiesQueuePayload(0)); _setMoveRatesPayloads.Add(new SetMoveRatesPayload(0, default)); _setMovementMultiplierPayloads.Add(new SetMovementMultiplierPayload(0)); _addTransformPropertiesPayloads.Add(new AddTransformPropertiesPayload(0, default)); _clearTransformPropertiesQueuePayloads.Add(new ClearTransformPropertiesQueuePayload(0)); _modifyTransformPropertiesPayloads.Add(new ModifyTransformPropertiesPayload(0, default, default)); _snapNonSmoothedPropertiesPayloads.Add(new SnapNonSmoothedPropertiesPayload(0, default)); _teleportPayloads.Add(new TeleportPayload(0)); Transform targetTransform = initializationSettings.TargetTransform; Transform graphicalTransform = initializationSettings.GraphicalTransform; if (!_trackerTransformsPool.TryPop(out Transform trackerTransform)) trackerTransform = new GameObject().transform; ProcessTransformsOnStart(trackerTransform, targetTransform, graphicalTransform, initializationSettings.DetachOnStart); _targetTaa.Add(targetTransform); _graphicalTaa.Add(graphicalTransform); _trackerTaa.Add(trackerTransform); //Use set method as it has sanity checks. SetInterpolationValue(smoother, ownerSettings.InterpolationValue, forOwnerOrOfflineSmoother: true, unsetAdaptiveInterpolation: false); SetInterpolationValue(smoother, spectatorSettings.InterpolationValue, forOwnerOrOfflineSmoother: false, unsetAdaptiveInterpolation: false); SetAdaptiveInterpolation(smoother, ownerSettings.AdaptiveInterpolationValue, forOwnerOrOfflineSmoother: true); SetAdaptiveInterpolation(smoother, spectatorSettings.AdaptiveInterpolationValue, forOwnerOrOfflineSmoother: false); } } /// /// Unregister a TickSmootherController. /// public void Unregister(TickSmootherController smoother) { using (_pm_Unregister.Auto()) { if (smoother == null || !_lookup.TryGetValue(smoother, out int index)) return; bool isDetachOnStart = _detachOnStartMask[index] != 0; bool isAttachOnStop = _attachOnStopMask[index] != 0; Transform targetTransform = _targetTaa[index]; Transform graphicalTransform = _graphicalTaa[index]; Transform trackerTransform = _trackerTaa[index]; ProcessTransformsOnStop(trackerTransform, targetTransform, graphicalTransform, isDetachOnStart, isAttachOnStop); if (trackerTransform) { _trackerTransformsPool.Push(trackerTransform); trackerTransform.SetParent(_trackerTransformsPoolHolder); } int last = _indexToSmoother.Count - 1; if (index != last) { var movedSmoother = _indexToSmoother[last]; _indexToSmoother[index] = movedSmoother; _lookup[movedSmoother] = index; var movedNetworkBehaviour = _indexToNetworkBehaviour[last]; _indexToNetworkBehaviour[index] = movedNetworkBehaviour; var movedPredictionNetworkTransform = _indexToPredictionNetworkTransform[last]; _indexToPredictionNetworkTransform[index] = movedPredictionNetworkTransform; } _indexToNetworkBehaviour.RemoveAt(last); _indexToPredictionNetworkTransform.RemoveAt(last); _indexToSmoother.RemoveAt(last); _lookup.Remove(smoother); _moveRates.RemoveAtSwapBack(index); _ownerSettings.RemoveAtSwapBack(index); _spectatorSettings.RemoveAtSwapBack(index); _preTickedMask.RemoveAtSwapBack(index); _canSmoothMask.RemoveAtSwapBack(index); _useOwnerSettingsMask.RemoveAtSwapBack(index); _objectReconcilingMask.RemoveAtSwapBack(index); _detachOnStartMask.RemoveAtSwapBack(index); _attachOnStopMask.RemoveAtSwapBack(index); _moveImmediatelyMask.RemoveAtSwapBack(index); _isMoving.RemoveAtSwapBack(index); _teleportedTick.RemoveAtSwapBack(index); _realTimeInterpolations.RemoveAtSwapBack(index); _movementMultipliers.RemoveAtSwapBack(index); _transformProperties.RemoveQueueAtSwapBack(index); _preTickGraphicSnapshot.RemoveAtSwapBack(index); _postTickTrackerSnapshot.RemoveAtSwapBack(index); _tempTargetSnapshot.RemoveAtSwapBack(index); _outSnapGraphicWorld.RemoveAtSwapBack(index); _outEnqueueTrackerWorld.RemoveAtSwapBack(index); _queuedTrackerProperties.RemoveAtSwapBack(index); _targetTaa.RemoveAtSwapBack(index); _graphicalTaa.RemoveAtSwapBack(index); _trackerTaa.RemoveAtSwapBack(index); _moveToTargetPayloads.RemoveAtSwapBack(index); _updateRealtimeInterpolationPayloads.RemoveAtSwapBack(index); _discardExcessivePayloads.RemoveAtSwapBack(index); _setMoveRatesPayloads.RemoveAtSwapBack(index); _setMovementMultiplierPayloads.RemoveAtSwapBack(index); _addTransformPropertiesPayloads.RemoveAtSwapBack(index); _clearTransformPropertiesQueuePayloads.RemoveAtSwapBack(index); _modifyTransformPropertiesPayloads.RemoveAtSwapBack(index); _snapNonSmoothedPropertiesPayloads.RemoveAtSwapBack(index); _teleportPayloads.RemoveAtSwapBack(index); } } /// /// Returns if configured transforms are valid. /// /// private static bool TransformsAreValid(Transform graphicalTransform, Transform targetTransform) { if (graphicalTransform == null) { NetworkManagerExtensions.LogError($"Graphical transform cannot be null."); return false; } if (targetTransform == null) { NetworkManagerExtensions.LogError($"Target transform on {graphicalTransform} cannot be null."); return false; } if (targetTransform == graphicalTransform) { NetworkManagerExtensions.LogError($"Target transform cannot be the same as graphical transform on {graphicalTransform}."); return false; } return true; } private static void ProcessTransformsOnStart(Transform trackerTransform, Transform targetTransform, Transform graphicalTransform, bool isDetachOnStart) { if (isDetachOnStart) { trackerTransform.SetParent(targetTransform); TransformProperties gfxWorldProperties = graphicalTransform.GetWorldProperties(); graphicalTransform.SetParent(null); graphicalTransform.SetWorldProperties(gfxWorldProperties); } else { Transform trackerParent = graphicalTransform.IsChildOf(targetTransform) ? graphicalTransform.parent : targetTransform; trackerTransform.SetParent(trackerParent); } targetTransform.GetPositionAndRotation(out var pos, out var rot); trackerTransform.SetWorldPositionRotationAndScale(pos, rot, graphicalTransform.localScale); trackerTransform.gameObject.name = $"{graphicalTransform.name}_Tracker"; } private static void ProcessTransformsOnStop(Transform trackerTransform, Transform targetTransform, Transform graphicalTransform, bool isDetachOnStart, bool isAttachOnStop) { if (trackerTransform == null || targetTransform == null || graphicalTransform == null) return; if (ApplicationState.IsQuitting()) return; trackerTransform.SetParent(null); if (isDetachOnStart && isAttachOnStop) { graphicalTransform.SetParent(targetTransform.parent); graphicalTransform.SetLocalProperties(trackerTransform.GetLocalProperties()); } } /// /// Updates movement settings for a registered smoother. /// Both owner and spectator settings are applied atomically. /// public void SetSettings(TickSmootherController smoother, in MovementSettings owner, in MovementSettings spectator) { if (smoother == null) return; if (!_lookup.TryGetValue(smoother, out int index)) return; _ownerSettings[index] = owner; _spectatorSettings[index] = spectator; } /// /// Sets transforms for a registered smoother (target, graphical, tracker). /// public void SetTransforms(TickSmootherController smoother, Transform target, Transform graphical) { if (smoother == null) return; if (!_lookup.TryGetValue(smoother, out int index)) return; bool isDetachOnStart = _detachOnStartMask[index] != 0; bool isAttachOnStop = _attachOnStopMask[index] != 0; Transform tracker = _trackerTaa[index]; Transform prevTarget = _targetTaa[index]; Transform prevGraphical = _graphicalTaa[index]; ProcessTransformsOnStop(tracker, prevTarget, prevGraphical, isDetachOnStart, isAttachOnStop); _targetTaa[index] = target; _graphicalTaa[index] = graphical; ProcessTransformsOnStart(tracker, target, graphical, isDetachOnStart); } /// /// Updates the smoothedProperties value. /// /// TickSmootherController. /// New value. /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings public void SetSmoothedProperties(TickSmootherController smoother, TransformPropertiesFlag value, bool forOwnerOrOfflineSmoother) { if (smoother == null) return; if (!_lookup.TryGetValue(smoother, out int index)) return; MovementSettings settings = forOwnerOrOfflineSmoother ? _ownerSettings[index] : _spectatorSettings[index]; settings.SmoothedProperties = value; if (forOwnerOrOfflineSmoother) _ownerSettings[index] = settings; else _spectatorSettings[index] = settings; } /// /// Updates the interpolationValue when not using adaptive interpolation. Calling this method will also disable adaptive interpolation. /// /// TickSmootherController. /// New value. /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings public void SetInterpolationValue(TickSmootherController smoother, byte value, bool forOwnerOrOfflineSmoother) => SetInterpolationValue(smoother, value, forOwnerOrOfflineSmoother, unsetAdaptiveInterpolation: true); /// /// Updates the interpolationValue when not using adaptive interpolation. Calling this method will also disable adaptive interpolation. /// /// TickSmootherController. /// New value. /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings /// private void SetInterpolationValue(TickSmootherController smoother, byte value, bool forOwnerOrOfflineSmoother, bool unsetAdaptiveInterpolation) { if (smoother == null) return; if (!_lookup.TryGetValue(smoother, out int index)) return; if (value < 1) value = 1; MovementSettings settings = forOwnerOrOfflineSmoother ? _ownerSettings[index] : _spectatorSettings[index]; settings.InterpolationValue = value; if (forOwnerOrOfflineSmoother) _ownerSettings[index] = settings; else _spectatorSettings[index] = settings; if (unsetAdaptiveInterpolation) SetAdaptiveInterpolation(smoother, AdaptiveInterpolationType.Off, forOwnerOrOfflineSmoother); } /// /// Updates the adaptiveInterpolation value. /// /// TickSmootherController. /// New value. /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings public void SetAdaptiveInterpolation(TickSmootherController smoother, AdaptiveInterpolationType value, bool forOwnerOrOfflineSmoother) { if (smoother == null) return; if (!_lookup.TryGetValue(smoother, out int index)) return; MovementSettings settings = forOwnerOrOfflineSmoother ? _ownerSettings[index] : _spectatorSettings[index]; settings.AdaptiveInterpolationValue = value; if (forOwnerOrOfflineSmoother) _ownerSettings[index] = settings; else _spectatorSettings[index] = settings; _updateRealtimeInterpolationPayloads[index] = new UpdateRealtimeInterpolationPayload(1); } /// /// Tries to set local properties for the graphical tracker transform. /// /// New values. /// Returns true if the tracker has been setup and values have been applied to teh tracker transform. /// When false is returned the values are cached and will be set when tracker is created. A cached value will be used every time the tracker is setup; to disable this behavior call this method with null value. public bool TrySetGraphicalTrackerLocalProperties(TickSmootherController smoother, TransformProperties? localValues) { if (smoother == null) return false; if (!_lookup.TryGetValue(smoother, out int index)) return false; if (_trackerTaa[index] == null || localValues == null) { _queuedTrackerProperties[index] = new NullableTransformProperties(localValues != null, localValues ?? default); return false; } _trackerTaa[index].SetLocalProperties(localValues.Value); return true; } public bool TryGetGraphicalTrackerLocalProperties(TickSmootherController smoother, out TransformProperties localValues) { localValues = default; if (smoother == null) return false; if (!_lookup.TryGetValue(smoother, out int index)) return false; Transform trackerTransform = _trackerTaa[index]; if (trackerTransform != null) { localValues = new(trackerTransform.localPosition, trackerTransform.localRotation, trackerTransform.localScale); return true; } NullableTransformProperties queuedTrackerProperties = _queuedTrackerProperties[index]; if (queuedTrackerProperties.IsExist != 0) { localValues = queuedTrackerProperties.Properties; return true; } // Fall through. return false; } /// /// Marks to teleports the graphical to it's starting position and clears the internal movement queue at the PreTick. /// public void Teleport(TickSmootherController smoother) { if (smoother == null) return; if (!_lookup.TryGetValue(smoother, out int index)) return; _teleportPayloads[index] = new TeleportPayload(1); } private void ClientManager_OnClientConnectionState(ClientConnectionStateArgs args) { using (_pm_ClientManager_OnClientConnectionState.Auto()) { while (_trackerTransformsPool.TryPop(out Transform trackerTransform)) if (trackerTransform && trackerTransform.gameObject) Destroy(trackerTransform.gameObject); if (args.ConnectionState == LocalConnectionState.Started) ChangeSubscriptions(true); else ChangeSubscriptions(false); } } private void ChangeSubscriptions(bool subscribe) { if (_timeManager == null) return; if (_subscribed == subscribe) return; _subscribed = subscribe; if (subscribe) { _timeManager.OnUpdate += TimeManager_OnUpdate; _timeManager.OnPreTick += TimeManager_OnPreTick; _timeManager.OnPostTick += TimeManager_OnPostTick; _timeManager.OnRoundTripTimeUpdated += TimeManager_OnRoundTripTimeUpdated; if (_predictionManager != null) _predictionManager.OnPostReplicateReplay += PredictionManager_OnPostReplicateReplay; } else { _timeManager.OnUpdate -= TimeManager_OnUpdate; _timeManager.OnPreTick -= TimeManager_OnPreTick; _timeManager.OnPostTick -= TimeManager_OnPostTick; _timeManager.OnRoundTripTimeUpdated -= TimeManager_OnRoundTripTimeUpdated; if (_predictionManager != null) _predictionManager.OnPostReplicateReplay -= PredictionManager_OnPostReplicateReplay; } } /// /// Called every frame. /// private void TimeManager_OnUpdate() { using (_pm_OnUpdate.Auto()) { int count = _indexToSmoother.Count; if (count == 0) return; int batchSize = ComputeBatchSize(count); var job = new UpdateJob { canSmoothMask = _canSmoothMask.AsArray(), deltaTime = Time.deltaTime, moveToTargetPayloads = _moveToTargetPayloads.AsArray() }; JobHandle innerHandle = job.Schedule(count, batchSize); JobHandle moveToTargetHandle = ScheduleMoveToTarget(innerHandle); moveToTargetHandle.Complete(); } } /// /// Called when the TimeManager invokes OnPreTick. /// private void TimeManager_OnPreTick() { using (_pm_OnPreTick.Auto()) { int count = _indexToSmoother.Count; if (count == 0) return; int batchSize = ComputeBatchSize(count); for (int i = 0; i < count; i++) { Transform graphicalTransform = _graphicalTaa[i]; NetworkBehaviour networkBehaviour = _indexToNetworkBehaviour[i]; NetworkTransform predictionNetworkTransform = _indexToPredictionNetworkTransform[i]; _canSmoothMask[i] = (byte)(graphicalTransform != null && (predictionNetworkTransform == null || !predictionNetworkTransform.DoSettingsAllowSmoothing()) && _networkManager.IsClientStarted ? 1 : 0); _useOwnerSettingsMask[i] = (byte)(networkBehaviour == null || networkBehaviour.IsOwner || !networkBehaviour.Owner.IsValid ? 1 : 0); _objectReconcilingMask[i] = (byte)(networkBehaviour == null || networkBehaviour.NetworkObject == null || networkBehaviour.NetworkObject.IsObjectReconciling ? 1 : 0); } JobHandle preTickMarkHandle = new PreTickMarkJob { canSmoothMask = _canSmoothMask.AsArray(), preTickedMask = _preTickedMask.AsArray(), discardExcessivePayloads = _discardExcessivePayloads.AsArray() }.Schedule(count, batchSize); JobHandle discardExcessiveHandle = ScheduleDiscardExcessiveTransformPropertiesQueue(preTickMarkHandle); JobHandle teleportHandle = ScheduleTeleport(discardExcessiveHandle); JobHandle preTickCaptureGraphicalHandle = new PreTickCaptureGraphicalJob { canSmoothMask = _canSmoothMask.AsArray(), useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), ownerSettings = _ownerSettings.AsArray(), spectatorSettings = _spectatorSettings.AsArray(), graphicSnapshot = _preTickGraphicSnapshot.AsArray() }.Schedule(_graphicalTaa, teleportHandle); preTickCaptureGraphicalHandle.Complete(); } } /// /// Called when the TimeManager invokes OnPostReplay. /// /// Replay tick for the local client. /// /// This is dependent on the initializing NetworkBehaviour being set. private void PredictionManager_OnPostReplicateReplay(uint clientTick, uint serverTick) { using (_pm_Prediction_OnPostReplicateReplay.Auto()) { int count = _indexToSmoother.Count; if (count == 0) return; int batchSize = ComputeBatchSize(count); var job = new PostReplicateReplayJob { clientTick = clientTick, teleportedTick = _teleportedTick.AsArray(), objectReconcilingMask = _objectReconcilingMask.AsArray(), transformProperties = _transformProperties, modifyTransformPropertiesPayloads = _modifyTransformPropertiesPayloads.AsArray() }; JobHandle innerHandle = job.Schedule(count, batchSize); JobHandle modifyTransformPropertiesHandle = ScheduleModifyTransformProperties(innerHandle); modifyTransformPropertiesHandle.Complete(); } } /// /// Called when TimeManager invokes OnPostTick. /// private void TimeManager_OnPostTick() { using (_pm_OnPostTick.Auto()) { int count = _indexToSmoother.Count; if (count == 0) return; JobHandle captureLocalTargetHandle = new CaptureLocalTargetJob { canSmoothMask = _canSmoothMask.AsArray(), useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), ownerSettings = _ownerSettings.AsArray(), spectatorSettings = _spectatorSettings.AsArray(), targetSnapshot = _tempTargetSnapshot.AsArray() }.Schedule(_targetTaa); JobHandle postTickCaptureTrackerHandle = new PostTickCaptureTrackerJob { canSmoothMask = _canSmoothMask.AsArray(), detachOnStartMask = _detachOnStartMask.AsArray(), useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), ownerSettings = _ownerSettings.AsArray(), spectatorSettings = _spectatorSettings.AsArray(), targetSnapshot = _tempTargetSnapshot.AsArray(), trackerSnapshot = _postTickTrackerSnapshot.AsArray() }.Schedule(_trackerTaa, captureLocalTargetHandle); JobHandle postTickHandle = new PostTickJob { clientTick = _timeManager.LocalTick, canSmoothMask = _canSmoothMask.AsArray(), teleportedTick = _teleportedTick.AsArray(), preTickedMask = _preTickedMask.AsArray(), detachOnStartMask = _detachOnStartMask.AsArray(), postTickTrackerSnapshot = _postTickTrackerSnapshot.AsArray(), preTickGraphicSnapshot = _preTickGraphicSnapshot.AsArray(), useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), ownerSettings = _ownerSettings.AsArray(), spectatorSettings = _spectatorSettings.AsArray(), discardExcessivePayloads = _discardExcessivePayloads.AsArray(), snapNonSmoothedPropertiesPayloads = _snapNonSmoothedPropertiesPayloads.AsArray(), addTransformPropertiesPayloads = _addTransformPropertiesPayloads.AsArray() }.Schedule(_graphicalTaa, postTickCaptureTrackerHandle); JobHandle discardExcessiveHandle = ScheduleDiscardExcessiveTransformPropertiesQueue(postTickHandle); JobHandle snapNonSmoothedPropertiesHandle = ScheduleSnapNonSmoothedProperties(discardExcessiveHandle); JobHandle addTransformPropertiesHandle = ScheduleAddTransformProperties(snapNonSmoothedPropertiesHandle); addTransformPropertiesHandle.Complete(); } } private void TimeManager_OnRoundTripTimeUpdated(long rttMs) { using (_pm_TimeManager_OnRoundTripTimeUpdated.Auto()) { int count = _indexToSmoother.Count; if (count == 0) return; int batchSize = ComputeBatchSize(count); var job = new RoundTripTimeUpdatedJob { useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), ownerSettings = _ownerSettings.AsArray(), spectatorSettings = _spectatorSettings.AsArray(), updateRealtimeInterpolationPayloads = _updateRealtimeInterpolationPayloads.AsArray() }; JobHandle innerHandle = job.Schedule(count, batchSize); JobHandle updateRealtimeInterpolationHandle = ScheduleUpdateRealtimeInterpolation(innerHandle); updateRealtimeInterpolationHandle.Complete(); } } /// /// Moves transform to target values. /// public JobHandle ScheduleMoveToTarget(in JobHandle outerHandle = default) { using (_pm_MoveToTarget.Auto()) { int count = _indexToSmoother.Count; if (count == 0) return outerHandle; var job = new MoveToTargetJob { jobPayloads = _moveToTargetPayloads.AsArray(), useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), ownerSettings = _ownerSettings.AsArray(), spectatorSettings = _spectatorSettings.AsArray(), realTimeInterpolations = _realTimeInterpolations.AsArray(), moveImmediatelyMask = _moveImmediatelyMask.AsArray(), tickDelta = (float)_timeManager.TickDelta, isMoving = _isMoving.AsArray(), movementMultipliers = _movementMultipliers.AsArray(), transformProperties = _transformProperties, moveRates = _moveRates.AsArray(), setMoveRatesPayloads = _setMoveRatesPayloads.AsArray(), setMovementMultiplierPayloads = _setMovementMultiplierPayloads.AsArray(), clearTransformPropertiesQueuePayloads = _clearTransformPropertiesQueuePayloads.AsArray() }; JobHandle innerHandle = job.Schedule(_graphicalTaa, outerHandle); return innerHandle; } } /// /// Updates interpolation based on localClient latency when using adaptive interpolation, or uses set value when adaptive interpolation is off. /// public JobHandle ScheduleUpdateRealtimeInterpolation(in JobHandle outerHandle = default) { using (_pm_ScheduleUpdateRealtimeInterpolation.Auto()) { int count = _indexToSmoother.Count; if (count == 0) return outerHandle; int batchSize = ComputeBatchSize(count); var job = new UpdateRealtimeInterpolationJob { jobPayloads = _updateRealtimeInterpolationPayloads.AsArray(), useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), ownerSettings = _ownerSettings.AsArray(), spectatorSettings = _spectatorSettings.AsArray(), tickDelta = (float)_timeManager.TickDelta, tickRate = _timeManager.TickRate, rtt = _timeManager.RoundTripTime, localTick = _timeManager.LocalTick, isServerOnlyStarted = _networkManager.IsServerOnlyStarted, realTimeInterpolations = _realTimeInterpolations.AsArray() }; JobHandle innerHandle = job.Schedule(count, batchSize, outerHandle); return innerHandle; } } /// /// Discards datas over interpolation limit from movement queue. /// private JobHandle ScheduleDiscardExcessiveTransformPropertiesQueue(in JobHandle outerHandle = default) { using (_pm_ScheduleDiscardExcessiveTransformPropertiesQueue.Auto()) { int count = _indexToSmoother.Count; if (count == 0) return outerHandle; int batchSize = ComputeBatchSize(count); var job = new DiscardExcessiveTransformPropertiesQueueJob { jobPayloads = _discardExcessivePayloads.AsArray(), realTimeInterpolations = _realTimeInterpolations.AsArray(), requiredQueuedOverInterpolation = REQUIRED_QUEUED_OVER_INTERPOLATION, transformProperties = _transformProperties, setMoveRatesPayloads = _setMoveRatesPayloads.AsArray() }; JobHandle innerHandle = job.Schedule(count, batchSize, outerHandle); JobHandle setMoveRatesHandle = ScheduleSetMoveRates(innerHandle); return setMoveRatesHandle; } } /// /// Sets new rates based on next entries in transformProperties queue, against a supplied TransformProperties. /// private JobHandle ScheduleSetMoveRates(in JobHandle outerHandle = default) { using (_pm_ScheduleSetMoveRates.Auto()) { int count = _indexToSmoother.Count; if (count == 0) return outerHandle; int batchSize = ComputeBatchSize(count); var job = new SetMoveRatesJob { jobPayloads = _setMoveRatesPayloads.AsArray(), useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), ownerSettings = _ownerSettings.AsArray(), spectatorSettings = _spectatorSettings.AsArray(), transformProperties = _transformProperties, tickDelta = (float)_timeManager.TickDelta, moveRates = _moveRates.AsArray(), setMovementMultiplierPayloads = _setMovementMultiplierPayloads.AsArray(), }; JobHandle innerHandle = job.Schedule(count, batchSize, outerHandle); JobHandle setMovementMultiplierHandle = ScheduleSetMovementMultiplier(innerHandle); return setMovementMultiplierHandle; } } private JobHandle ScheduleSetMovementMultiplier(in JobHandle outerHandle = default) { using (_pm_ScheduleSetMovementMultiplier.Auto()) { int count = _indexToSmoother.Count; if (count == 0) return outerHandle; int batchSize = ComputeBatchSize(count); var job = new SetMovementMultiplierJob { jobPayloads = _setMovementMultiplierPayloads.AsArray(), transformProperties = _transformProperties, realTimeInterpolations = _realTimeInterpolations.AsArray(), moveImmediatelyMask = _moveImmediatelyMask.AsArray(), movementMultipliers = _movementMultipliers.AsArray() }; JobHandle innerHandle = job.Schedule(count, batchSize, outerHandle); return innerHandle; } } /// /// Adds a new transform properties and sets move rates if needed. /// private JobHandle ScheduleAddTransformProperties(JobHandle outerHandle) { using (_pm_ScheduleAddTransformProperties.Auto()) { int count = _indexToSmoother.Count; if (count == 0) return outerHandle; var job = new AddTransformPropertiesJob { jobPayloads = _addTransformPropertiesPayloads.AsArray(), useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), ownerSettings = _ownerSettings.AsArray(), spectatorSettings = _spectatorSettings.AsArray(), transformProperties = _transformProperties, setMoveRatesPayloads = _setMoveRatesPayloads.AsArray() }; JobHandle innerHandle = job.Schedule(_graphicalTaa, outerHandle); JobHandle setMoveRatesHandle = ScheduleSetMoveRates(innerHandle); return setMoveRatesHandle; } } /// /// Clears the pending movement queue. /// private JobHandle ScheduleClearTransformPropertiesQueue(JobHandle outerHandle) { using (_pm_ScheduleClearTransformPropertiesQueue.Auto()) { int count = _indexToSmoother.Count; if (count == 0) return outerHandle; int batchSize = ComputeBatchSize(count); var job = new ClearTransformPropertiesQueueJob { jobPayloads = _clearTransformPropertiesQueuePayloads.AsArray(), transformProperties = _transformProperties, moveRates = _moveRates.AsArray() }; JobHandle innerHandle = job.Schedule(count, batchSize, outerHandle); return innerHandle; } } /// /// Modifies a transform property for a tick. This does not error check for empty collections. /// firstTick - First tick in the queue. If 0 this will be looked up. /// private JobHandle ScheduleModifyTransformProperties(JobHandle outerHandle) { using (_pm_ScheduleModifyTransformProperties.Auto()) { int count = _indexToSmoother.Count; if (count == 0) return outerHandle; JobHandle captureLocalTargetHandle = new CaptureLocalTargetJob { canSmoothMask = _canSmoothMask.AsArray(), targetSnapshot = _tempTargetSnapshot.AsArray(), useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), ownerSettings = _ownerSettings.AsArray(), spectatorSettings = _spectatorSettings.AsArray() }.Schedule(_targetTaa, outerHandle); JobHandle modifyTransformPropertiesHandle = new ModifyTransformPropertiesJob { jobPayloads = _modifyTransformPropertiesPayloads.AsArray(), detachOnStartMask = _detachOnStartMask.AsArray(), useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), ownerSettings = _ownerSettings.AsArray(), spectatorSettings = _spectatorSettings.AsArray(), targetSnapshot = _tempTargetSnapshot.AsArray(), transformProperties = _transformProperties, }.Schedule(_trackerTaa, captureLocalTargetHandle); return modifyTransformPropertiesHandle; } } /// /// Snaps non-smoothed properties to original positoin if setting is enabled. /// private JobHandle ScheduleSnapNonSmoothedProperties(JobHandle outerHandle) { using (_pm_ScheduleSnapNonSmoothedProperties.Auto()) { int count = _indexToSmoother.Count; if (count == 0) return outerHandle; var job = new SnapNonSmoothedPropertiesJob { jobPayloads = _snapNonSmoothedPropertiesPayloads.AsArray(), useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), ownerSettings = _ownerSettings.AsArray(), spectatorSettings = _spectatorSettings.AsArray(), }; JobHandle innerHandle = job.Schedule(_graphicalTaa, outerHandle); return innerHandle; } } /// /// Teleports the graphical to it's starting position and clears the internal movement queue. /// private JobHandle ScheduleTeleport(JobHandle outerHandle) { using (_pm_ScheduleTeleport.Auto()) { int count = _indexToSmoother.Count; if (count == 0) return outerHandle; var job = new TeleportJob { jobPayloads = _teleportPayloads.AsArray(), useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), ownerSettings = _ownerSettings.AsArray(), spectatorSettings = _spectatorSettings.AsArray(), preTickTrackerSnapshot = _postTickTrackerSnapshot.AsArray(), localTick = _timeManager.LocalTick, transformProperties = _transformProperties, clearTransformPropertiesQueuePayloads = _clearTransformPropertiesQueuePayloads.AsArray(), moveRates = _moveRates.AsArray(), teleportedTick = _teleportedTick.AsArray() }; JobHandle innerHandle = job.Schedule(_graphicalTaa, outerHandle); JobHandle clearTransformPropertiesQueueHandle = ScheduleClearTransformPropertiesQueue(innerHandle); return clearTransformPropertiesQueueHandle; } } private static int ComputeBatchSize(int length, int minBatch = 1, int maxBatch = 128) { if (length <= 0) return 1; // +1: main thread + worker threads int workers = JobsUtility.JobWorkerCount + 1; // Aim for ~4 waves of batches across all workers. int targetBatches = Mathf.Max(1, workers * 4); // CeilDiv to get iterations per batch int batch = (length + targetBatches - 1) / targetBatches; return Mathf.Clamp(batch, minBatch, maxBatch); } } } #endif