Skip to content

Conversation

@NoelStephensUnity
Copy link
Collaborator

@NoelStephensUnity NoelStephensUnity commented Nov 21, 2025

Purpose of this PR

Provide an alternate way to handle this (pending standards for public API passing).

Jira ticket

NA

Changelog

NA

Documentation

NA

Testing & QA (How your changes can be verified during release Playtest)

NA

Functional Testing

Manual testing :

  • Manual testing done

Automated tests:

  • Covered by existing automated tests
  • Covered by new automated tests

Does the change require QA team to:

  • Review automated tests?
  • Execute manual tests?
  • Provide feedback about the PR?

If any boxes above are checked the QA team will be automatically added as a PR reviewer.

Backports

No back port needed

@NoelStephensUnity NoelStephensUnity requested a review from a team as a code owner November 21, 2025 21:55
@NoelStephensUnity NoelStephensUnity changed the base branch from chore/optimize-network-transform-state to develop-2.0.0 November 21, 2025 21:55
adding comment
Updating some tests with these changes
(WIP) Migrating away from direct transform access and using a cached transform approach.
Migrating all of the flags into FlagStates.
Migrating the bit serialization into FlagStates.
Migrating flag helper methods into FlagStates.
This resolves the issue with the EntityId no longer supporting integer conversion.
removing partial comment and whitespace.
Another spot where EntityId was needed.
Missed SwitchTransformSpaceWhenParented.
Since MP Tools subscribes to the TransportInitialized and TransportDisposed methods and we have to vet against packages with dependencies, I am putting a temporary work-around to get past this issue so we can land the EntityId fix.
Once MP Tools is updated for EntityId, we can re-enable the original fix.
Found a few more places that could be micro-optimized.
Fixing missed transformToUse that was if-def'd out.
@EmandM
Copy link
Collaborator

EmandM commented Nov 24, 2025

These changes are merged into #3770

@EmandM EmandM closed this Nov 24, 2025
@EmandM
Copy link
Collaborator

EmandM commented Nov 25, 2025

/review

@u-pr-agent
Copy link

u-pr-agent bot commented Nov 25, 2025

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪

The PR involves a significant refactor of the `NetworkTransformState` struct and internal logic of `NetworkTransform`, requiring careful verification of bit-packing compatibility and state management.
🏅 Score: 88

The refactor improves performance and code structure, but there are missed opportunities for using the new `CachedTransform` field consistently, and a potentially unsafe cast in `UnityTransport`.
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Issue

The cast (int)entityId.GetRawData() might result in data loss or ID collisions if EntityId (in Unity 6) exceeds the range of a 32-bit integer. This could affect subscribers of TransportInitialized (like Multiplayer Tools) if they rely on unique or complete IDs.

TransportInitialized?.Invoke((int)entityId.GetRawData(), m_Driver);
Missed Optimization

There are several places where transform.parent or transform.position is used instead of the newly introduced CachedTransform. Using CachedTransform consistently would avoid the overhead of the transform property access.
Examples include lines 2043, 2222, 2674, 2678, 3057, and 3301.

                    if (SwitchTransformSpaceWhenParented && m_LocalAuthoritativeNetworkState.ExplicitSet && m_LocalAuthoritativeNetworkState.FlagStates.IsDirty && transform.parent != null && !m_LocalAuthoritativeNetworkState.InLocalSpace)
                    {
                        InLocalSpace = true;
                        CheckForStateChange(ref m_LocalAuthoritativeNetworkState, synchronize, forceState: true);
                    }
                }

                // Send the state update
                UpdateTransformState();

                // Mark the last tick and the old state (for next ticks)
                m_OldState = m_LocalAuthoritativeNetworkState;

                // Preserve our teleporting flag in order to know if the state pushed was a teleport
                m_LocalAuthoritativeNetworkState.FlagStates.WasTeleported = m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame;

                // Reset the teleport and explicit state flags after we have sent the state update.
                // These could be set again in the below OnAuthorityPushTransformState virtual method
                m_LocalAuthoritativeNetworkState.FlagStates.IsTeleportingNextFrame = false;
                m_LocalAuthoritativeNetworkState.ExplicitSet = false;

                try
                {
                    // Notify of the pushed state update
                    OnAuthorityPushTransformState(ref m_LocalAuthoritativeNetworkState);
                }
                catch (Exception ex)
                {
                    Debug.LogException(ex);
                }

                // The below is part of assuring we only send a frame synch, when sending unreliable deltas, if
                // we have already sent at least one unreliable delta state update. At this point in the callstack,
                // a delta state update has just been sent in the above UpdateTransformState() call and as long as
                // we didn't send a frame synch and we are not synchronizing then we know at least one unreliable
                // delta has been sent. Under this scenario, we should start checking for this instance's alloted
                // frame synch "tick slot". Once we send a frame synch, if no other deltas occur after that
                // (i.e. the object is at rest) then we will stop sending frame synch's until the object begins
                // moving, rotating, or scaling again.
                if (UseUnreliableDeltas && !m_LocalAuthoritativeNetworkState.FlagStates.UnreliableFrameSync && !synchronize)
                {
                    m_DeltaSynch = true;
                }

#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
                // We handle updating attached bodies when the "parent" body has a state update in order to keep their delta state updates tick synchronized.
                if (m_UseRigidbodyForMotion && m_NetworkRigidbodyInternal.NetworkRigidbodyConnections.Count > 0)
                {
                    foreach (var childRigidbody in m_NetworkRigidbodyInternal.NetworkRigidbodyConnections)
                    {
                        childRigidbody.NetworkTransform.OnNetworkTick(true);
                    }
                }
#endif
                // When enabled, any children will get tick synchronized with state updates
                if (TickSyncChildren)
                {
                    // Synchronize any nested NetworkTransforms with the parent's
                    foreach (var childNetworkTransform in NetworkObject.NetworkTransforms)
                    {
                        // Don't update the same instance or any nested NetworkTransform with a different authority mode
                        if (childNetworkTransform == this || childNetworkTransform.AuthorityMode != AuthorityMode)
                        {
                            continue;
                        }
                        if (childNetworkTransform.CanCommitToTransform)
                        {
                            childNetworkTransform.OnNetworkTick(true);
                        }
                    }

                    // Synchronize any parented children with the parent's motion
                    foreach (var child in m_ParentedChildren)
                    {
                        // Synchronize any nested NetworkTransforms of the child with the parent's
                        foreach (var childNetworkTransform in child.NetworkTransforms)
                        {
                            if (childNetworkTransform.CanCommitToTransform)
                            {
                                childNetworkTransform.OnNetworkTick(true);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Used for integration testing:
        /// Will apply the transform to the LocalAuthoritativeNetworkState and get detailed dirty information returned
        /// in the <see cref="NetworkTransformState"/> returned.
        /// </summary>
        /// <param name="transform">transform to apply</param>
        /// <returns>NetworkTransformState</returns>
        internal NetworkTransformState ApplyLocalNetworkState(Transform transform)
        {
            // Since we never commit these changes, we need to simulate that any changes were committed previously and the bitset
            // value would already be reset prior to having the state applied
            m_LocalAuthoritativeNetworkState.FlagStates.ClearBitSetForNextTick();

            // Now check the transform for any threshold value changes
            CheckForStateChange(ref m_LocalAuthoritativeNetworkState);

            // Return the entire state to be used by the integration test
            return m_LocalAuthoritativeNetworkState;
        }

        /// <summary>
        /// Used for integration testing
        /// </summary>
        internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkState, double dirtyTime, Transform transformToUse)
        {
            CachedTransform = transformToUse;
            m_CachedNetworkManager = NetworkManager;
            // Apply the interpolate and PostionDeltaCompression flags, otherwise we get false positives whether something changed or not.
            networkState.FlagStates.UseInterpolation = Interpolate;
            networkState.FlagStates.QuaternionSync = UseQuaternionSynchronization;
            networkState.FlagStates.UseHalfFloatPrecision = UseHalfFloatPrecision;
            networkState.FlagStates.QuaternionCompression = UseQuaternionCompression;
            networkState.FlagStates.UseUnreliableDeltas = UseUnreliableDeltas;
            m_HalfPositionState = new NetworkDeltaPosition(Vector3.zero, 0, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ));

            return CheckForStateChange(ref networkState);
        }

        /// <summary>
        /// Applies the transform to the <see cref="NetworkTransformState"/> specified.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private bool CheckForStateChange(ref NetworkTransformState networkState, bool isSynchronization = false, ulong targetClientId = 0, bool forceState = false)
        {
            var flagStates = networkState.FlagStates;

            // As long as we are not doing our first synchronization and we are sending unreliable deltas, each
            // NetworkTransform will stagger its full transfom synchronization over a 1 second period based on the
            // assigned tick slot (m_TickSync).
            // More about m_DeltaSynch:
            // If we have not sent any deltas since our last frame synch, then this will prevent us from sending
            // frame synch's when the object is at rest. If this is false and a state update is detected and sent,
            // then it will be set to true and each subsequent tick will do this check to determine if it should
            // send a full frame synch.
            var isAxisSync = false;
            // We compare against the NetworkTickSystem version since ServerTime is set when updating ticks
            if (UseUnreliableDeltas && !isSynchronization && m_DeltaSynch && m_NextTickSync <= CurrentTick)
            {
                // TODO-CACHE: m_CachedNetworkManager.NetworkConfig.TickRate value
                // Increment to the next frame synch tick position for this instance
                m_NextTickSync += (int)m_CachedNetworkManager.NetworkConfig.TickRate;
                // If we are teleporting, we do not need to send a frame synch for this tick slot
                // as a "frame synch" really is effectively just a teleport.
                isAxisSync = !flagStates.IsTeleportingNextFrame;
                // Reset our delta synch trigger so we don't send another frame synch until we
                // send at least 1 unreliable state update after this fame synch or teleport
                m_DeltaSynch = false;
            }

            // This is used to determine if we need to send the state update reliably (if we are doing an axial sync)
            flagStates.UnreliableFrameSync = isAxisSync;

            var isTeleportingAndNotSynchronizing = flagStates.IsTeleportingNextFrame && !isSynchronization;
            var isDirty = false;
            var isPositionDirty = isTeleportingAndNotSynchronizing ? flagStates.HasPositionChange : false;
            var isRotationDirty = isTeleportingAndNotSynchronizing ? flagStates.HasRotAngleChange : false;
            var isScaleDirty = isTeleportingAndNotSynchronizing ? flagStates.HasScaleChange : false;

            flagStates.SwitchTransformSpaceWhenParented = SwitchTransformSpaceWhenParented;



            // All of the checks below, up to the delta position checking portion, are to determine if the
            // authority changed a property during runtime that requires a full synchronizing.
#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
            if ((InLocalSpace != flagStates.InLocalSpace || isSynchronization) && !m_UseRigidbodyForMotion)
#else
            if (InLocalSpace != flagStates.InLocalSpace)
#endif
            {
                // When SwitchTransformSpaceWhenParented is set we automatically set our local space based on whether
                // we are parented or not.
                flagStates.InLocalSpace = SwitchTransformSpaceWhenParented ? transform.parent != null : InLocalSpace;
                if (SwitchTransformSpaceWhenParented)
                {
                    InLocalSpace = flagStates.InLocalSpace;
                }
                isDirty = true;

                // If we are already teleporting preserve the teleport flag.
                // If we don't have SwitchTransformSpaceWhenParented set or we are synchronizing,
                // then set the teleport flag.
                flagStates.IsTeleportingNextFrame |= !SwitchTransformSpaceWhenParented || isSynchronization;

                // Otherwise, if SwitchTransformSpaceWhenParented is set we force a full state update.
                // If interpolation is enabled, then any non-authority instance will update any pending
                // buffered values to the correct world or local space values.
                forceState = SwitchTransformSpaceWhenParented;
            }


#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
            var position = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : InLocalSpace ? CachedTransform.localPosition : CachedTransform.position;
            var rotation = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetRotation() : InLocalSpace ? CachedTransform.localRotation : CachedTransform.rotation;

            var positionThreshold = Vector3.one * PositionThreshold;
            var rotationThreshold = Vector3.one * RotAngleThreshold;

            // NSS: Disabling this for the time being
            // TODO: Determine if we actually need this and if not remove this from NetworkRigidBodyBase
            //if (m_UseRigidbodyForMotion)
            //{
            //    positionThreshold = m_NetworkRigidbodyInternal.GetAdjustedPositionThreshold();
            //    rotationThreshold = m_NetworkRigidbodyInternal.GetAdjustedRotationThreshold();
            //}
#else
            var position = InLocalSpace ? CachedTransform.localPosition : CachedTransform.position;
            var rotation = InLocalSpace ? CachedTransform.localRotation : CachedTransform.rotation;
            var positionThreshold = Vector3.one * PositionThreshold;
            var rotationThreshold = Vector3.one * RotAngleThreshold;
#endif
            var rotAngles = rotation.eulerAngles;
            var scale = CachedTransform.localScale;
            flagStates.IsSynchronizing = isSynchronization;

            // Check for parenting when synchronizing and/or teleporting
            if (isSynchronization || flagStates.IsTeleportingNextFrame || forceState)
            {
                // This all has to do with complex nested hierarchies and how it impacts scale
                // when set for the first time or teleporting and depends upon whether the
                // NetworkObject is parented (or "de-parented") at the same time any scale
                // values are applied.
                var hasParentNetworkObject = false;

                var parentNetworkObject = (NetworkObject)null;

                // If the NetworkObject belonging to this NetworkTransform instance has a parent
                // (i.e. this handles nested NetworkTransforms under a parent at some layer above)
                if (NetworkObject.transform.parent != null)
                {
                    parentNetworkObject = NetworkObject.transform.parent.GetComponent<NetworkObject>();

                    // In-scene placed NetworkObjects parented under a GameObject with no
                    // NetworkObject preserve their lossyScale when synchronizing.
                    if (parentNetworkObject == null && NetworkObject.IsSceneObject != false)
                    {
                        hasParentNetworkObject = true;
                    }
                    else
                    {
                        // Or if the relative NetworkObject has a parent NetworkObject
                        hasParentNetworkObject = parentNetworkObject != null;
                    }
                }

                flagStates.IsParented = hasParentNetworkObject;
            }

            if (Interpolate != flagStates.UseInterpolation)
            {
                flagStates.UseInterpolation = Interpolate;
                isDirty = true;
                // When we change from interpolating to not interpolating (or vice versa) we need to synchronize/reset everything
                flagStates.IsTeleportingNextFrame = true;
            }

            if (UseQuaternionSynchronization != flagStates.QuaternionSync)
            {
                flagStates.QuaternionSync = UseQuaternionSynchronization;
                isDirty = true;
                flagStates.IsTeleportingNextFrame = true;
            }

            if (UseQuaternionCompression != flagStates.QuaternionCompression)
            {
                flagStates.QuaternionCompression = UseQuaternionCompression;
                isDirty = true;
                flagStates.IsTeleportingNextFrame = true;
            }

            if (UseHalfFloatPrecision != flagStates.UseHalfFloatPrecision)
            {
                flagStates.UseHalfFloatPrecision = UseHalfFloatPrecision;
                isDirty = true;
                flagStates.IsTeleportingNextFrame = true;
            }

            if (SlerpPosition != flagStates.UsePositionSlerp)
            {
                flagStates.UsePositionSlerp = SlerpPosition;
                isDirty = true;
                flagStates.IsTeleportingNextFrame = true;
            }

            if (UseUnreliableDeltas != flagStates.UseUnreliableDeltas)
            {
                flagStates.UseUnreliableDeltas = UseUnreliableDeltas;
                isDirty = true;
                flagStates.IsTeleportingNextFrame = true;
            }

            // Begin delta checks against last sent state update
            if (!UseHalfFloatPrecision)
            {
                if (SyncPositionX && (Mathf.Abs(networkState.PositionX - position.x) >= positionThreshold.x || flagStates.IsTeleportingNextFrame || isAxisSync || forceState))
                {
                    networkState.PositionX = position.x;
                    flagStates.SetHasPosition(Axis.X, true);
                    isPositionDirty = true;
                }

                if (SyncPositionY && (Mathf.Abs(networkState.PositionY - position.y) >= positionThreshold.y || flagStates.IsTeleportingNextFrame || isAxisSync || forceState))
                {
                    networkState.PositionY = position.y;
                    flagStates.SetHasPosition(Axis.Y, true);
                    isPositionDirty = true;
                }

                if (SyncPositionZ && (Mathf.Abs(networkState.PositionZ - position.z) >= positionThreshold.z || flagStates.IsTeleportingNextFrame || isAxisSync || forceState))
                {
                    networkState.PositionZ = position.z;
                    flagStates.SetHasPosition(Axis.Z, true);
                    isPositionDirty = true;
                }
            }
            else if (SynchronizePosition)
            {
                // If we are teleporting then we can skip the delta threshold check
                isPositionDirty = flagStates.IsTeleportingNextFrame || isAxisSync || forceState;
                if (m_HalfFloatTargetTickOwnership > CurrentTick)
                {
                    isPositionDirty = true;
                }

                // For NetworkDeltaPosition, if any axial value is dirty then we always send a full update
                if (!isPositionDirty)
                {
                    for (int i = 0; i < 3; i++)
                    {
                        if (Math.Abs(position[i] - m_HalfPositionState.PreviousPosition[i]) >= positionThreshold[i])
                        {
                            isPositionDirty = i == 0 ? SyncPositionX : i == 1 ? SyncPositionY : SyncPositionZ;
                            if (!isPositionDirty)
                            {
                                continue;
                            }
                            break;
                        }
                    }
                }

                // If the position is dirty or we are teleporting (which includes synchronization)
                // then determine what parts of the NetworkDeltaPosition should be updated
                if (isPositionDirty)
                {
                    // If we are not synchronizing the transform state for the first time
                    if (!isSynchronization)
                    {
                        // With global teleporting (broadcast to all non-authority instances)
                        // we re-initialize authority's NetworkDeltaPosition and synchronize all
                        // non-authority instances with the new full precision position
                        if (flagStates.IsTeleportingNextFrame)
                        {
                            m_HalfPositionState = new NetworkDeltaPosition(position, networkState.NetworkTick, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ));
                            networkState.CurrentPosition = position;
                        }
                        else // Otherwise, just synchronize the delta position value
                        {
                            m_HalfPositionState.HalfVector3.AxisToSynchronize = math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ);
                            m_HalfPositionState.UpdateFrom(ref position, networkState.NetworkTick);
                        }

                        networkState.NetworkDeltaPosition = m_HalfPositionState;

                        // If ownership offset is greater or we are doing an axial synchronization then synchronize the base position
                        if ((m_HalfFloatTargetTickOwnership > CurrentTick || isAxisSync) && !flagStates.IsTeleportingNextFrame)
                        {
                            flagStates.SynchronizeBaseHalfFloat = true;
                        }
                        else
                        {
                            flagStates.SynchronizeBaseHalfFloat = UseUnreliableDeltas ? m_HalfPositionState.CollapsedDeltaIntoBase : false;
                        }
                    }
                    else // If synchronizing is set, then use the current full position value on the server side
                    {
                        if (ShouldSynchronizeHalfFloat(targetClientId))
                        {
                            // If we have a NetworkDeltaPosition that has a state applied, then we want to determine
                            // what needs to be synchronized. For owner authoritative mode, the server side
                            // will have no valid state yet.
                            if (m_HalfPositionState.NetworkTick > 0)
                            {
                                // Always synchronize the base position and the ushort values of the
                                // current m_HalfPositionState
                                networkState.CurrentPosition = m_HalfPositionState.CurrentBasePosition;
                                networkState.NetworkDeltaPosition = m_HalfPositionState;
                                // If the server is the owner, in both server and owner authoritative modes,
                                // or we are running in server authoritative mode, then we use the
                                // HalfDeltaConvertedBack value as the delta position
                                if (NetworkObject.IsOwnedByServer || IsServerAuthoritative())
                                {
                                    networkState.DeltaPosition = m_HalfPositionState.HalfDeltaConvertedBack;
                                }
                                else
                                {
                                    // Otherwise, we are in owner authoritative mode and the server's NetworkDeltaPosition
                                    // state is "non-authoritative" relative so we use the DeltaPosition.
                                    networkState.DeltaPosition = m_HalfPositionState.DeltaPosition;
                                }
                            }
                            else // Reset everything and just send the current position
                            {
                                networkState.NetworkDeltaPosition = new NetworkDeltaPosition(Vector3.zero, 0, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ));
                                networkState.DeltaPosition = Vector3.zero;
                                networkState.CurrentPosition = position;
                            }
                        }
                        else
                        {
                            networkState.NetworkDeltaPosition = new NetworkDeltaPosition(Vector3.zero, 0, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ));
                            networkState.CurrentPosition = position;
                        }
                        // Add log entry for this update relative to the client being synchronized
                        AddLogEntry(ref networkState, targetClientId, true);
                    }
                    flagStates.HasPositionX = SyncPositionX;
                    flagStates.HasPositionY = SyncPositionY;
                    flagStates.HasPositionZ = SyncPositionZ;
                    flagStates.HasPositionChange = SyncPositionX || SyncPositionY || SyncPositionZ;
                }
            }

            if (!UseQuaternionSynchronization)
            {
                if (SyncRotAngleX && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) >= rotationThreshold.x || flagStates.IsTeleportingNextFrame || isAxisSync || forceState))
                {
                    networkState.RotAngleX = rotAngles.x;
                    flagStates.SetHasRotation(Axis.X, true);
                    isRotationDirty = true;
                }

                if (SyncRotAngleY && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) >= rotationThreshold.y || flagStates.IsTeleportingNextFrame || isAxisSync || forceState))
                {
                    networkState.RotAngleY = rotAngles.y;
                    flagStates.SetHasRotation(Axis.Y, true);
                    isRotationDirty = true;
                }

                if (SyncRotAngleZ && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) >= rotationThreshold.z || flagStates.IsTeleportingNextFrame || isAxisSync || forceState))
                {
                    networkState.RotAngleZ = rotAngles.z;
                    flagStates.SetHasRotation(Axis.Z, true);
                    isRotationDirty = true;
                }
            }
            else if (SynchronizeRotation)
            {
                // If we are teleporting then we can skip the delta threshold check
                isRotationDirty = flagStates.IsTeleportingNextFrame || isAxisSync || forceState;
                // For quaternion synchronization, if one angle is dirty we send a full update
                if (!isRotationDirty)
                {
                    var previousRotation = networkState.Rotation.eulerAngles;
                    for (int i = 0; i < 3; i++)
                    {
                        if (Mathf.Abs(Mathf.DeltaAngle(previousRotation[i], rotAngles[i])) >= rotationThreshold[i])
                        {
                            isRotationDirty = true;
                            break;
                        }
                    }
                }
                if (isRotationDirty)
                {
                    networkState.Rotation = rotation;
                    flagStates.MarkChanged(AxialType.Rotation, true);
                }
            }

            // For scale, we need to check for parenting when synchronizing and/or teleporting (synchronization is always teleporting)
            if (flagStates.IsTeleportingNextFrame)
            {
                // If we are synchronizing and the associated NetworkObject has a parent then we want to send the
                // LossyScale if the NetworkObject has a parent since NetworkObject spawn order is not guaranteed
                if (flagStates.IsParented)
                {
                    networkState.LossyScale = CachedTransform.lossyScale;
                }
            }

            // Checking scale deltas when not synchronizing
            if (!isSynchronization)
            {
                if (!UseHalfFloatPrecision)
                {
                    if (SyncScaleX && (Mathf.Abs(networkState.ScaleX - scale.x) >= ScaleThreshold || flagStates.IsTeleportingNextFrame || isAxisSync || forceState))
                    {
                        networkState.ScaleX = scale.x;
                        flagStates.SetHasScale(Axis.X, true);
                        isScaleDirty = true;
                    }

                    if (SyncScaleY && (Mathf.Abs(networkState.ScaleY - scale.y) >= ScaleThreshold || flagStates.IsTeleportingNextFrame || isAxisSync || forceState))
                    {
                        networkState.ScaleY = scale.y;
                        flagStates.SetHasScale(Axis.Y, true);
                        isScaleDirty = true;
                    }

                    if (SyncScaleZ && (Mathf.Abs(networkState.ScaleZ - scale.z) >= ScaleThreshold || flagStates.IsTeleportingNextFrame || isAxisSync || forceState))
                    {
                        networkState.ScaleZ = scale.z;
                        flagStates.SetHasScale(Axis.Z, true);
                        isScaleDirty = true;
                    }
                }
                else if (SynchronizeScale)
                {
                    var previousScale = networkState.Scale;
                    for (int i = 0; i < 3; i++)
                    {
                        if (Mathf.Abs(scale[i] - previousScale[i]) >= ScaleThreshold || flagStates.IsTeleportingNextFrame || isAxisSync || forceState)
                        {
                            isScaleDirty = true;
                            networkState.Scale[i] = scale[i];
                            flagStates.SetHasScale((Axis)i, i == 0 ? SyncScaleX : i == 1 ? SyncScaleY : SyncScaleZ);
                        }
                    }
                }
            }
            else // Just apply the full local scale when synchronizing
            if (SynchronizeScale)
            {
                var localScale = CachedTransform.localScale;
                if (!UseHalfFloatPrecision)
                {

                    networkState.ScaleX = localScale.x;
                    networkState.ScaleY = localScale.y;
                    networkState.ScaleZ = localScale.z;
                }
                else
                {
                    networkState.Scale = localScale;
                }
                flagStates.MarkChanged(AxialType.Scale, true);
                isScaleDirty = true;
            }
            isDirty |= isPositionDirty || isRotationDirty || isScaleDirty;

            if (isDirty)
            {
                // Some integration/unit tests disable the NetworkTransform and there is no
                // NetworkManager
                if (enabled)
                {
                    // We use the NetworkTickSystem version since ServerTime is set when updating ticks
                    networkState.NetworkTick = CurrentTick;
                }
            }

            // Mark the state dirty for the next network tick update to clear out the bitset values
            flagStates.IsDirty |= isDirty;

            // Apply any flag state changes
            networkState.FlagStates = flagStates;
            return isDirty;
        }

        /// <summary>
        /// Authority subscribes to network tick events and will invoke
        /// <see cref="OnUpdateAuthoritativeState(ref Transform)"/> each network tick.
        /// </summary>
        private void OnNetworkTick(bool isCalledFromParent = false)
        {
            // If not active, then ignore the update
            if (!gameObject.activeInHierarchy)
            {
                return;
            }

            // As long as we are still authority
            if (CanCommitToTransform)
            {
                if (m_CachedNetworkManager.DistributedAuthorityMode && !IsOwner)
                {
                    Debug.LogError($"Non-owner Client-{m_CachedNetworkManager.LocalClientId} is being updated by network tick still!!!!");
                    return;
                }

                // If we are nested and have already sent a state update this tick, then exit early (otherwise check for any changes in state)
                if (IsNested && m_LocalAuthoritativeNetworkState.NetworkTick == CurrentTick)
                {
                    return;
                }

#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
                // Let the parent handle the updating of this to keep the two synchronized
                if (!isCalledFromParent && m_UseRigidbodyForMotion && m_NetworkRigidbodyInternal.ParentBody != null && !m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame)
                {
                    return;
                }
#endif

                // Update any changes to the transform based on the current state
                OnUpdateAuthoritativeState(isCalledFromParent);
#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
                m_InternalCurrentPosition = m_LastStateTargetPosition = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : GetSpaceRelativePosition();
                m_InternalCurrentRotation = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetRotation() : GetSpaceRelativeRotation();
                m_TargetRotation = m_InternalCurrentRotation.eulerAngles;
#else
                m_InternalCurrentPosition = GetSpaceRelativePosition();
                m_LastStateTargetPosition = GetSpaceRelativePosition();
#endif
            }
            else // If we are no longer authority, unsubscribe to the tick event
            {
                DeregisterForTickUpdate(this);
            }
        }
        #endregion

        #region NON-AUTHORITY STATE UPDATE

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal void UpdatePositionInterpolator(Vector3 position, double time, bool resetInterpolator = false)
        {
            if (!CanCommitToTransform)
            {
                if (resetInterpolator)
                {
                    m_PositionInterpolator.AutoConvertTransformSpace = SwitchTransformSpaceWhenParented;
                    m_PositionInterpolator.InLocalSpace = InLocalSpace;
                    m_PositionInterpolator.ResetTo(transform.parent, position, time);
                }
                else
                {
                    m_PositionInterpolator.AddMeasurement(transform.parent, position, time);
                }
            }
        }

        internal bool LogMotion;

        /// <summary>
        /// Virtual method invoked on the non-authority side after a new state has been received and applied.
        /// </summary>
        protected virtual void OnTransformUpdated()
        {

        }

        /// <summary>
        /// Applies the authoritative state to the transform
        /// </summary>
        protected internal void ApplyAuthoritativeState()
        {
#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
            // TODO: Make this an authority flag
            // For now, just synchronize with the NetworkRigidbodyBase UseRigidBodyForMotion
            if (m_NetworkRigidbodyInternal != null)
            {
                m_UseRigidbodyForMotion = m_NetworkRigidbodyInternal.UseRigidBodyForMotion;
            }
#endif
            var networkState = m_LocalAuthoritativeNetworkState;
            var flagStates = m_LocalAuthoritativeNetworkState.FlagStates;
            // The m_InternalCurrentPosition, m_InternalCurrentRotation, and m_InternalCurrentScale values are continually updated
            // at the end of this method and assure that when not interpolating the non-authoritative side
            // cannot make adjustments to any portions the transform not being synchronized.
            var adjustedPosition = m_InternalCurrentPosition;
            var currentPosition = GetSpaceRelativePosition();
            adjustedPosition.x = SyncPositionX ? m_InternalCurrentPosition.x : currentPosition.x;
            adjustedPosition.y = SyncPositionY ? m_InternalCurrentPosition.y : currentPosition.y;
            adjustedPosition.z = SyncPositionZ ? m_InternalCurrentPosition.z : currentPosition.z;

            var adjustedRotation = m_InternalCurrentRotation;
            var adjustedRotAngles = adjustedRotation.eulerAngles;
            var currentRotation = GetSpaceRelativeRotation().eulerAngles;
            adjustedRotAngles.x = SyncRotAngleX ? adjustedRotAngles.x : currentRotation.x;
            adjustedRotAngles.y = SyncRotAngleY ? adjustedRotAngles.y : currentRotation.y;
            adjustedRotAngles.z = SyncRotAngleZ ? adjustedRotAngles.z : currentRotation.z;
            adjustedRotation.eulerAngles = adjustedRotAngles;


            var adjustedScale = m_InternalCurrentScale;
            var currentScale = GetScale();
            adjustedScale.x = SyncScaleX ? adjustedScale.x : currentScale.x;
            adjustedScale.y = SyncScaleY ? adjustedScale.y : currentScale.y;
            adjustedScale.z = SyncScaleZ ? adjustedScale.z : currentScale.z;

            // Only if SwitchTransformSpaceWhenParented is not enabled should
            // non-authority instances preserve the current state's local space
            // setting.
            if (!SwitchTransformSpaceWhenParented)
            {
                InLocalSpace = flagStates.InLocalSpace;
            }

            // Non-Authority Preservers the authority's transform state update modes
            Interpolate = flagStates.UseInterpolation;
            UseHalfFloatPrecision = flagStates.UseHalfFloatPrecision;
            UseQuaternionSynchronization = flagStates.QuaternionSync;
            UseQuaternionCompression = flagStates.QuaternionCompression;
            UseUnreliableDeltas = flagStates.UseUnreliableDeltas;

            if (SlerpPosition != flagStates.UsePositionSlerp)
            {
                SlerpPosition = flagStates.UsePositionSlerp;
                UpdatePositionSlerp();
            }

            // NOTE ABOUT INTERPOLATING AND THE CODE BELOW:
            // We always apply the interpolated state for any axis we are synchronizing even when the state has no deltas
            // to assure we fully interpolate to our target even after we stop extrapolating 1 tick later.

            if (Interpolate)
            {
                if (SynchronizePosition)
                {
                    var interpolatedPosition = m_PositionInterpolator.GetInterpolatedValue();
                    if (UseHalfFloatPrecision)
                    {
                        adjustedPosition = interpolatedPosition;
                    }
                    else
                    {
                        if (SyncPositionX) { adjustedPosition.x = interpolatedPosition.x; }
                        if (SyncPositionY) { adjustedPosition.y = interpolatedPosition.y; }
                        if (SyncPositionZ) { adjustedPosition.z = interpolatedPosition.z; }
                    }
                }

                if (SynchronizeScale)
                {
                    if (UseHalfFloatPrecision)
                    {
                        adjustedScale = m_ScaleInterpolator.GetInterpolatedValue();
                    }
                    else
                    {
                        var interpolatedScale = m_ScaleInterpolator.GetInterpolatedValue();
                        if (SyncScaleX) { adjustedScale.x = interpolatedScale.x; }
                        if (SyncScaleY) { adjustedScale.y = interpolatedScale.y; }
                        if (SyncScaleZ) { adjustedScale.z = interpolatedScale.z; }
                    }
                }

                if (SynchronizeRotation)
                {
                    var interpolatedRotation = m_RotationInterpolator.GetInterpolatedValue();
                    if (UseQuaternionSynchronization)
                    {
                        adjustedRotation = interpolatedRotation;
                    }
                    else
                    {
                        var interpolatedEulerAngles = interpolatedRotation.eulerAngles;
                        if (SyncRotAngleX) { adjustedRotAngles.x = interpolatedEulerAngles.x; }
                        if (SyncRotAngleY) { adjustedRotAngles.y = interpolatedEulerAngles.y; }
                        if (SyncRotAngleZ) { adjustedRotAngles.z = interpolatedEulerAngles.z; }
                        adjustedRotation.eulerAngles = adjustedRotAngles;
                    }
                }
            }
            else
            {
                // Non-Interpolated Position and Scale
                if (UseHalfFloatPrecision)
                {
                    if (flagStates.HasPositionChange && SynchronizePosition)
                    {
                        adjustedPosition = m_LastStateTargetPosition;
                    }

                    if (flagStates.HasScaleChange && SynchronizeScale)
                    {
                        for (int i = 0; i < 3; i++)
                        {
                            if (m_LocalAuthoritativeNetworkState.FlagStates.HasScale((Axis)i))
                            {
                                adjustedScale[i] = m_LocalAuthoritativeNetworkState.Scale[i];
                            }
                        }
                    }
                }
                else
                {
                    if (flagStates.HasPositionX) { adjustedPosition.x = networkState.PositionX; }
                    if (flagStates.HasPositionY) { adjustedPosition.y = networkState.PositionY; }
                    if (flagStates.HasPositionZ) { adjustedPosition.z = networkState.PositionZ; }
                    if (flagStates.HasScaleX) { adjustedScale.x = networkState.ScaleX; }
                    if (flagStates.HasScaleY) { adjustedScale.y = networkState.ScaleY; }
                    if (flagStates.HasScaleZ) { adjustedScale.z = networkState.ScaleZ; }
                }

                // Non-interpolated rotation
                if (SynchronizeRotation)
                {
                    if (flagStates.QuaternionSync && flagStates.HasRotAngleChange)
                    {
                        adjustedRotation = networkState.Rotation;
                    }
                    else
                    {
                        if (flagStates.HasRotAngleX) { adjustedRotAngles.x = networkState.RotAngleX; }
                        if (flagStates.HasRotAngleY) { adjustedRotAngles.y = networkState.RotAngleY; }
                        if (flagStates.HasRotAngleZ) { adjustedRotAngles.z = networkState.RotAngleZ; }
                        adjustedRotation.eulerAngles = adjustedRotAngles;
                    }
                }
            }

            // Apply the position if we are synchronizing position
            if (SynchronizePosition)
            {
                // Update our current position if it changed or we are interpolating
                if (flagStates.HasPositionChange || Interpolate)
                {
                    if (SyncPositionX && SyncPositionY && SyncPositionZ)
                    {
                        m_InternalCurrentPosition = adjustedPosition;
                    }
                    else
                    {
                        // Preserve any non-synchronized changes to the local instance's position
                        var position = InLocalSpace ? CachedTransform.localPosition : CachedTransform.position;
                        m_InternalCurrentPosition.x = SyncPositionX ? adjustedPosition.x : position.x;
                        m_InternalCurrentPosition.y = SyncPositionY ? adjustedPosition.y : position.y;
                        m_InternalCurrentPosition.z = SyncPositionZ ? adjustedPosition.z : position.z;
                    }
                }
#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
                if (m_UseRigidbodyForMotion)
                {
                    m_NetworkRigidbodyInternal.MovePosition(m_InternalCurrentPosition);
                    if (LogMotion)
                    {
                        Debug.Log($"[Client-{m_CachedNetworkManager.LocalClientId}][Interpolate: {networkState.UseInterpolation}][TransPos: {transform.position}][RBPos: {m_NetworkRigidbodyInternal.GetPosition()}][CurrentPos: {m_InternalCurrentPosition}");
                    }

                }
                else
#endif
                {
                    if (PositionInLocalSpace)
                    {
                        CachedTransform.localPosition = m_InternalCurrentPosition;
                    }
                    else
                    {
                        CachedTransform.position = m_InternalCurrentPosition;
                    }
                }
            }

            // Apply the rotation if we are synchronizing rotation
            if (SynchronizeRotation)
            {
                // Update our current rotation if it changed or we are interpolating
                if (networkState.HasRotAngleChange || Interpolate)
                {
                    if ((SyncRotAngleX && SyncRotAngleY && SyncRotAngleZ) || UseQuaternionSynchronization)
                    {
                        m_InternalCurrentRotation = adjustedRotation;
                    }
                    else
                    {
                        // Preserve any non-synchronized changes to the local instance's rotation
                        var rotation = InLocalSpace ? CachedTransform.localRotation.eulerAngles : CachedTransform.rotation.eulerAngles;
                        var currentEuler = m_InternalCurrentRotation.eulerAngles;
                        var updatedEuler = adjustedRotation.eulerAngles;
                        currentEuler.x = SyncRotAngleX ? updatedEuler.x : rotation.x;
                        currentEuler.y = SyncRotAngleY ? updatedEuler.y : rotation.y;
                        currentEuler.z = SyncRotAngleZ ? updatedEuler.z : rotation.z;
                        m_InternalCurrentRotation.eulerAngles = currentEuler;
                    }
                }

#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
                if (m_UseRigidbodyForMotion)
                {
                    m_NetworkRigidbodyInternal.MoveRotation(m_InternalCurrentRotation);
                }
                else
#endif
                {
                    if (RotationInLocalSpace)
                    {
                        CachedTransform.localRotation = m_InternalCurrentRotation;
                    }
                    else
                    {
                        CachedTransform.rotation = m_InternalCurrentRotation;
                    }
                }
            }

            // Apply the scale if we are synchronizing scale
            if (SynchronizeScale)
            {
                // Update our current scale if it changed or we are interpolating
                if (flagStates.HasScaleChange || Interpolate)
                {
                    if (SyncScaleX && SyncScaleY && SyncScaleZ)
                    {
                        m_InternalCurrentScale = adjustedScale;
                    }
                    else
                    {
                        // Preserve any non-synchronized changes to the local instance's scale
                        var scale = CachedTransform.localScale;
                        m_InternalCurrentScale.x = SyncScaleX ? adjustedScale.x : scale.x;
                        m_InternalCurrentScale.y = SyncScaleY ? adjustedScale.y : scale.y;
                        m_InternalCurrentScale.z = SyncScaleZ ? adjustedScale.z : scale.z;
                    }
                }
                CachedTransform.localScale = m_InternalCurrentScale;
            }
            OnTransformUpdated();
        }

        /// <summary>
        /// Handles applying the full authoritative state (i.e. teleporting)
        /// </summary>
        /// <remarks>
        /// Only non-authoritative instances should invoke this
        /// </remarks>
        private void ApplyTeleportingState(NetworkTransformState newState)
        {
            if (!newState.IsTeleportingNextFrame)
            {
                return;
            }

            var sentTime = newState.SentTime;
            var currentPosition = GetSpaceRelativePosition();
            var currentRotation = GetSpaceRelativeRotation();
            var currentEulerAngles = currentRotation.eulerAngles;
            var currentScale = CachedTransform.localScale;

            var isSynchronization = newState.IsSynchronizing;
            var flagStates = newState.FlagStates;

            // Clear all interpolators
            m_ScaleInterpolator.Clear();
            m_PositionInterpolator.Clear();
            m_RotationInterpolator.Clear();

            if (flagStates.HasPositionChange)
            {
                if (!UseHalfFloatPrecision)
                {
                    // Adjust based on which axis changed
                    if (flagStates.HasPositionX)
                    {
                        currentPosition.x = newState.PositionX;
                    }

                    if (flagStates.HasPositionY)
                    {
                        currentPosition.y = newState.PositionY;
                    }

                    if (flagStates.HasPositionZ)
                    {
                        currentPosition.z = newState.PositionZ;
                    }
                }
                else
                {
                    // With delta position teleport updates or synchronization, we create a new instance and provide the current network tick.
                    m_HalfPositionState = new NetworkDeltaPosition(newState.CurrentPosition, newState.NetworkTick, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ));

                    // When first synchronizing we determine if we need to apply the current delta position
                    // offset or not. This is specific to owner authoritative mode on the owner side only
                    if (isSynchronization)
                    {
                        // Need to use NetworkManager vs m_CachedNetworkManager here since we are yet to be spawned
                        if (ShouldSynchronizeHalfFloat(NetworkManager.LocalClientId))
                        {
                            m_HalfPositionState.HalfVector3.Axis = newState.NetworkDeltaPosition.HalfVector3.Axis;
                            m_HalfPositionState.DeltaPosition = newState.DeltaPosition;
                            currentPosition = m_HalfPositionState.ToVector3(newState.NetworkTick);
                        }
                        else
                        {
                            currentPosition = newState.CurrentPosition;
                        }
                        // Before the state is applied add a log entry if AddLogEntry is assigned
                        AddLogEntry(ref newState, NetworkObject.OwnerClientId, true);
                    }
                    else
                    {
                        // If we are just teleporting, then we already created a new NetworkDeltaPosition value.
                        // set the current position to the state's current position
                        currentPosition = newState.CurrentPosition;
                    }
                }

                m_InternalCurrentPosition = currentPosition;
                m_LastStateTargetPosition = currentPosition;

                // Apply the position
                if (flagStates.InLocalSpace)
                {
                    CachedTransform.localPosition = currentPosition;
                }
                else
                {
                    CachedTransform.position = currentPosition;
                }

#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
                if (m_UseRigidbodyForMotion)
                {
                    m_NetworkRigidbodyInternal.SetPosition(transform.position);
                }
#endif

                if (Interpolate)
                {
                    UpdatePositionInterpolator(currentPosition, sentTime, true);
                }
            }

            if (flagStates.HasScaleChange)
            {
                bool shouldUseLossy = false;

                if (UseHalfFloatPrecision)
                {
                    currentScale = shouldUseLossy ? newState.LossyScale : newState.Scale;
                }
                else
                {
                    // Adjust based on which axis changed
                    if (flagStates.HasScaleX)
                    {
                        currentScale.x = shouldUseLossy ? newState.LossyScale.x : newState.ScaleX;
                    }

                    if (flagStates.HasScaleY)
                    {
                        currentScale.y = shouldUseLossy ? newState.LossyScale.y : newState.ScaleY;
                    }

                    if (flagStates.HasScaleZ)
                    {
                        currentScale.z = shouldUseLossy ? newState.LossyScale.z : newState.ScaleZ;
                    }
                }

                m_InternalCurrentScale = currentScale;
                m_TargetScale = currentScale;

                // Apply the adjusted scale
                CachedTransform.localScale = currentScale;

                if (Interpolate)
                {
                    m_ScaleInterpolator.ResetTo(currentScale, sentTime);
                }
            }

            if (flagStates.HasRotAngleChange)
            {
                if (flagStates.QuaternionSync)
                {
                    currentRotation = newState.Rotation;
                }
                else
                {
                    // Adjust based on which axis changed
                    if (flagStates.HasRotAngleX)
                    {
                        currentEulerAngles.x = newState.RotAngleX;
                    }

                    if (flagStates.HasRotAngleY)
                    {
                        currentEulerAngles.y = newState.RotAngleY;
                    }

                    if (flagStates.HasRotAngleZ)
                    {
                        currentEulerAngles.z = newState.RotAngleZ;
                    }
                    currentRotation.eulerAngles = currentEulerAngles;
                }

                m_InternalCurrentRotation = currentRotation;
                m_TargetRotation = currentRotation.eulerAngles;

                if (InLocalSpace)
                {
                    CachedTransform.localRotation = currentRotation;
                }
                else
                {
                    CachedTransform.rotation = currentRotation;
                }

#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
                if (m_UseRigidbodyForMotion)
                {
                    m_NetworkRigidbodyInternal.SetRotation(CachedTransform.rotation);
                }
#endif

                if (Interpolate)
                {
                    m_RotationInterpolator.AutoConvertTransformSpace = SwitchTransformSpaceWhenParented;
                    m_RotationInterpolator.InLocalSpace = newState.InLocalSpace;
                    m_RotationInterpolator.ResetTo(CachedTransform.parent, currentRotation, sentTime);
                }
            }

            // Add log after to applying the update if AddLogEntry is defined
            if (isSynchronization)
            {
                AddLogEntry(ref newState, NetworkObject.OwnerClientId);
            }

            OnTransformUpdated();
        }


        internal int LastTickSync = 0;
        /// <summary>
        /// Adds the new state's values to their respective interpolator
        /// </summary>
        /// <remarks>
        /// Only non-authoritative instances should invoke this
        /// </remarks>
        internal void ApplyUpdatedState(NetworkTransformState newState)
        {
            var flagStates = newState.FlagStates;
            // Set the transforms's synchronization modes
            InLocalSpace = flagStates.InLocalSpace;
            Interpolate = flagStates.UseInterpolation;
            UseQuaternionSynchronization = flagStates.QuaternionSync;
            UseQuaternionCompression = flagStates.QuaternionCompression;
            UseHalfFloatPrecision = flagStates.UseHalfFloatPrecision;
            UseUnreliableDeltas = flagStates.UseUnreliableDeltas;

            SwitchTransformSpaceWhenParented = flagStates.SwitchTransformSpaceWhenParented;

            if (SlerpPosition != flagStates.UsePositionSlerp)
            {
                SlerpPosition = flagStates.UsePositionSlerp;
                UpdatePositionSlerp();
            }

            m_LocalAuthoritativeNetworkState = newState;
            if (flagStates.IsTeleportingNextFrame)
            {
                LastTickSync = m_LocalAuthoritativeNetworkState.GetNetworkTick();
                ApplyTeleportingState(m_LocalAuthoritativeNetworkState);
                return;
            }
            else if (flagStates.IsSynchronizing)
            {
                LastTickSync = m_LocalAuthoritativeNetworkState.GetNetworkTick();
            }

            var sentTime = newState.SentTime;
            var currentRotation = GetSpaceRelativeRotation();

            // Only if using half float precision and our position had changed last update then
            if (UseHalfFloatPrecision && flagStates.HasPositionChange)
            {
                // Do a full precision synchronization to apply the base position and offset.
                if (flagStates.SynchronizeBaseHalfFloat)
                {
                    m_HalfPositionState = m_LocalAuthoritativeNetworkState.NetworkDeltaPosition;
                }
                else
                {
                    // assure our local NetworkDeltaPosition state is updated
                    m_HalfPositionState.HalfVector3.Axis = m_LocalAuthoritativeNetworkState.NetworkDeltaPosition.HalfVector3.Axis;
                    m_LocalAuthoritativeNetworkState.NetworkDeltaPosition.CurrentBasePosition = m_HalfPositionState.CurrentBasePosition;

                    // This is to assure when you get the position of the state it is the correct position
                    m_LocalAuthoritativeNetworkState.NetworkDeltaPosition.ToVector3(0);
                }
                // Update the target position for this incoming state.
                // This becomes the last known received state position (unlike interpolators that will have a queue).
                m_LastStateTargetPosition = m_HalfPositionState.ToVector3(newState.NetworkTick);
                m_LocalAuthoritativeNetworkState.CurrentPosition = m_LastStateTargetPosition;
            }

            if (!Interpolate)
            {
                ApplyAuthoritativeState();
                return;
            }

            // Apply axial changes from the new state
            // Either apply the delta position target position or the current state's delta position
            // depending upon whether UsePositionDeltaCompression is enabled
            if (flagStates.HasPositionChange)
            {
                // If interpolating, get the current value as the final next position or current position
                // depending upon if the interpolator is still processing a state or not.
                if (!flagStates.UseHalfFloatPrecision)
                {
                    var newTargetPosition = (Interpolate && SwitchTransformSpaceWhenParented) ? m_PositionInterpolator.GetInterpolatedValue() : m_LastStateTargetPosition;
                    var position = m_LocalAuthoritativeNetworkState.GetPosition();
                    if (flagStates.HasPositionX)
                    {
                        newTargetPosition.x = position.x;
                    }

                    if (flagStates.HasPositionY)
                    {
                        newTargetPosition.y = position.y;
                    }

                    if (flagStates.HasPositionZ)
                    {
                        newTargetPosition.z = position.z;
                    }
                    m_LastStateTargetPosition = newTargetPosition;
                }

                UpdatePositionInterpolator(m_LastStateTargetPosition, sentTime);
            }

            if (flagStates.HasScaleChange)
            {
                var currentScale = m_TargetScale;
                if (UseHalfFloatPrecision)
                {
                    for (int i = 0; i < 3; i++)
                    {
                        if (m_LocalAuthoritativeNetworkState.FlagStates.HasScale((Axis)i))
                        {
                            currentScale[i] = m_LocalAuthoritativeNetworkState.Scale[i];
                        }
                    }
                }
                else
                {
                    if (flagStates.HasScaleX)
                    {
                        currentScale.x = m_LocalAuthoritativeNetworkState.ScaleX;
                    }

                    if (flagStates.HasScaleY)
                    {
                        currentScale.y = m_LocalAuthoritativeNetworkState.ScaleY;
                    }

                    if (flagStates.HasScaleZ)
                    {
                        currentScale.z = m_LocalAuthoritativeNetworkState.ScaleZ;
                    }
                }
                m_TargetScale = currentScale;
                m_ScaleInterpolator.AddMeasurement(transform.parent, currentScale, sentTime);
Side Effect in Test Helper

The method ApplyTransformToNetworkState sets CachedTransform = transformToUse. If this method is used with a temporary or mock transform (as in tests), it permanently changes the CachedTransform reference for the component, which might lead to incorrect behavior if the component is subsequently used in the same runtime context.

CachedTransform = transformToUse;
  • Update review

🤖 Helpful? Please react with 👍/👎 | Questions❓Please reach out in Slack #ask-u-pr-agent

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants