Skip to content

Commit 45c68a8

Browse files
fix: ClientNetworkTransform ownership change with half precision synchronization issues [MTT-7271] (#2712)
* update Adding OnOwnershipChanged(ulong previous, ulong current) * fix This resolves the issue with client authoritative network transforms and the random "noise" that would occur when transitioning ownership from a remote client back to the host-server. - Latent messages from the client would still be received and processed after ownership changed. - Ownership changed messages would proceed the NetworkTransform initialization state update message. Now ownership changed messages precede the NetworkTransform initialization state update message. - Clients could sometimes have the same network tick value even when the tick event had triggered, which for NetworkDeltaPosition would cause dropped state updates.
1 parent 4e8f2d8 commit 45c68a8

File tree

6 files changed

+103
-18
lines changed

6 files changed

+103
-18
lines changed

com.unity.netcode.gameobjects/Components/NetworkTransform.cs

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,10 @@ public Quaternion GetRotation()
499499
/// <remarks>
500500
/// When there is no change in an updated state's position then there are no values to return.
501501
/// Checking for <see cref="HasPositionChange"/> is one way to detect this.
502+
/// When used with half precision it returns the half precision delta position state update
503+
/// which will not be the full position.
504+
/// To get a NettworkTransform's full position, use <see cref="GetSpaceRelativePosition(bool)"/> and
505+
/// pass true as the parameter.
502506
/// </remarks>
503507
/// <returns><see cref="Vector3"/></returns>
504508
public Vector3 GetPosition()
@@ -1110,7 +1114,16 @@ public Vector3 GetSpaceRelativePosition(bool getCurrentState = false)
11101114
}
11111115
else
11121116
{
1113-
return m_CurrentPosition;
1117+
// When half float precision is enabled, get the NetworkDeltaPosition's full position
1118+
if (UseHalfFloatPrecision)
1119+
{
1120+
return m_HalfPositionState.GetFullPosition();
1121+
}
1122+
else
1123+
{
1124+
// Otherwise, just get the current position
1125+
return m_CurrentPosition;
1126+
}
11141127
}
11151128
}
11161129

@@ -1393,6 +1406,8 @@ protected virtual void OnAuthorityPushTransformState(ref NetworkTransformState n
13931406
{
13941407
}
13951408

1409+
// Tracks the last tick a state update was sent (see further below)
1410+
private int m_LastTick;
13961411
/// <summary>
13971412
/// Authoritative side only
13981413
/// If there are any transform delta states, this method will synchronize the
@@ -1411,11 +1426,27 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz
14111426
if (ApplyTransformToNetworkStateWithInfo(ref m_LocalAuthoritativeNetworkState, ref transformToCommit, synchronize))
14121427
{
14131428
m_LocalAuthoritativeNetworkState.LastSerializedSize = m_OldState.LastSerializedSize;
1414-
OnAuthorityPushTransformState(ref m_LocalAuthoritativeNetworkState);
14151429

1430+
// Make sure our network tick is incremented
1431+
if (m_LastTick == m_LocalAuthoritativeNetworkState.NetworkTick && !m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame)
1432+
{
1433+
// When running in authority and a remote client is the owner, the client can hit a perfect window of time where
1434+
// it is still on the previous network tick (as a count) but still have had the tick event triggered.
1435+
// (This is cheaper than calculating the exact tick each time and only can occur on clients)
1436+
if (!IsServer)
1437+
{
1438+
m_LocalAuthoritativeNetworkState.NetworkTick = m_LocalAuthoritativeNetworkState.NetworkTick + 1;
1439+
}
1440+
else
1441+
{
1442+
NetworkLog.LogError($"[NT TICK DUPLICATE] Server already sent an update on tick {m_LastTick} and is attempting to send again on the same network tick!");
1443+
}
1444+
}
1445+
m_LastTick = m_LocalAuthoritativeNetworkState.NetworkTick;
14161446
// Update the state
14171447
UpdateTransformState();
14181448

1449+
OnAuthorityPushTransformState(ref m_LocalAuthoritativeNetworkState);
14191450
m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = false;
14201451
}
14211452
}
@@ -2209,6 +2240,7 @@ private void ApplyUpdatedState(NetworkTransformState newState)
22092240
m_HalfPositionState.HalfVector3.Axis = m_LocalAuthoritativeNetworkState.NetworkDeltaPosition.HalfVector3.Axis;
22102241
// and update our target position
22112242
m_TargetPosition = m_HalfPositionState.ToVector3(newState.NetworkTick);
2243+
m_LocalAuthoritativeNetworkState.NetworkDeltaPosition.CurrentBasePosition = m_HalfPositionState.CurrentBasePosition;
22122244
m_LocalAuthoritativeNetworkState.CurrentPosition = m_TargetPosition;
22132245
}
22142246

@@ -2455,6 +2487,9 @@ private void NetworkTickSystem_Tick()
24552487
// Update any changes to the transform
24562488
var transformSource = transform;
24572489
OnUpdateAuthoritativeState(ref transformSource);
2490+
2491+
m_CurrentPosition = GetSpaceRelativePosition();
2492+
m_TargetPosition = GetSpaceRelativePosition();
24582493
}
24592494
else
24602495
{
@@ -2466,9 +2501,6 @@ private void NetworkTickSystem_Tick()
24662501
}
24672502
}
24682503

2469-
2470-
2471-
24722504
/// <inheritdoc/>
24732505
public override void OnNetworkSpawn()
24742506
{
@@ -2510,24 +2542,25 @@ public override void OnDestroy()
25102542
}
25112543

25122544
/// <inheritdoc/>
2513-
public override void OnGainedOwnership()
2545+
public override void OnLostOwnership()
25142546
{
2515-
// Only initialize if we gained ownership
2516-
if (OwnerClientId == NetworkManager.LocalClientId)
2517-
{
2518-
Initialize();
2519-
}
2547+
base.OnLostOwnership();
25202548
}
25212549

25222550
/// <inheritdoc/>
2523-
public override void OnLostOwnership()
2551+
public override void OnGainedOwnership()
25242552
{
2525-
// Only initialize if we are not authority and lost
2526-
// ownership
2527-
if (OwnerClientId != NetworkManager.LocalClientId)
2553+
base.OnGainedOwnership();
2554+
}
2555+
2556+
protected override void OnOwnershipChanged(ulong previous, ulong current)
2557+
{
2558+
// If we were the previous owner or the newly assigned owner then reinitialize
2559+
if (current == NetworkManager.LocalClientId || previous == NetworkManager.LocalClientId)
25282560
{
25292561
Initialize();
25302562
}
2563+
base.OnOwnershipChanged(previous, current);
25312564
}
25322565

25332566
/// <summary>
@@ -2552,6 +2585,7 @@ protected virtual void OnInitialize(ref NetworkVariable<NetworkTransformState> r
25522585

25532586
}
25542587

2588+
25552589
/// <summary>
25562590
/// Initializes NetworkTransform when spawned and ownership changes.
25572591
/// </summary>
@@ -2572,7 +2606,8 @@ protected void Initialize()
25722606
{
25732607
m_HalfPositionState = new NetworkDeltaPosition(currentPosition, NetworkManager.NetworkTickSystem.ServerTime.Tick, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ));
25742608
}
2575-
2609+
m_CurrentPosition = currentPosition;
2610+
m_TargetPosition = currentPosition;
25762611
// Authority only updates once per network tick
25772612
NetworkManager.NetworkTickSystem.Tick -= NetworkTickSystem_Tick;
25782613
NetworkManager.NetworkTickSystem.Tick += NetworkTickSystem_Tick;
@@ -2835,6 +2870,13 @@ public bool IsServerAuthoritative()
28352870
/// <param name="messagePayload">serialzied <see cref="NetworkTransformState"/></param>
28362871
private void TransformStateUpdate(ulong senderId, FastBufferReader messagePayload)
28372872
{
2873+
if (!OnIsServerAuthoritative() && IsServer && OwnerClientId == NetworkManager.ServerClientId)
2874+
{
2875+
// Ownership must have changed, ignore any additional pending messages that might have
2876+
// come from a previous owner client.
2877+
return;
2878+
}
2879+
28382880
// Forward owner authoritative messages before doing anything else
28392881
if (IsServer && !OnIsServerAuthoritative())
28402882
{
@@ -2856,6 +2898,7 @@ private void TransformStateUpdate(ulong senderId, FastBufferReader messagePayloa
28562898
/// <param name="messagePayload">the owner state message payload</param>
28572899
private unsafe void ForwardStateUpdateMessage(FastBufferReader messagePayload)
28582900
{
2901+
var serverAuthoritative = OnIsServerAuthoritative();
28592902
var currentPosition = messagePayload.Position;
28602903
var messageSize = messagePayload.Length - currentPosition;
28612904
var writer = new FastBufferWriter(messageSize, Allocator.Temp);
@@ -2867,7 +2910,7 @@ private unsafe void ForwardStateUpdateMessage(FastBufferReader messagePayload)
28672910
for (int i = 0; i < clientCount; i++)
28682911
{
28692912
var clientId = NetworkManager.ConnectionManager.ConnectedClientsList[i].ClientId;
2870-
if (!OnIsServerAuthoritative() && (NetworkManager.ServerClientId == clientId || clientId == OwnerClientId))
2913+
if (NetworkManager.ServerClientId == clientId || (!serverAuthoritative && clientId == OwnerClientId))
28712914
{
28722915
continue;
28732916
}

com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,23 @@ internal void InternalOnGainedOwnership()
532532
OnGainedOwnership();
533533
}
534534

535+
/// <summary>
536+
/// Invoked on all clients, override this method to be notified of any
537+
/// ownership changes (even if the instance was niether the previous or
538+
/// newly assigned current owner).
539+
/// </summary>
540+
/// <param name="previous">the previous owner</param>
541+
/// <param name="current">the current owner</param>
542+
protected virtual void OnOwnershipChanged(ulong previous, ulong current)
543+
{
544+
545+
}
546+
547+
internal void InternalOnOwnershipChanged(ulong previous, ulong current)
548+
{
549+
OnOwnershipChanged(previous, current);
550+
}
551+
535552
/// <summary>
536553
/// Gets called when we loose ownership of this object
537554
/// </summary>

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,21 @@ internal void InvokeBehaviourOnGainedOwnership()
840840
}
841841
}
842842

843+
internal void InvokeOwnershipChanged(ulong previous, ulong next)
844+
{
845+
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
846+
{
847+
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
848+
{
849+
ChildNetworkBehaviours[i].InternalOnOwnershipChanged(previous, next);
850+
}
851+
else
852+
{
853+
Debug.LogWarning($"{ChildNetworkBehaviours[i].gameObject.name} is disabled! Netcode for GameObjects does not support disabled NetworkBehaviours! The {ChildNetworkBehaviours[i].GetType().Name} component was skipped during ownership assignment!");
854+
}
855+
}
856+
}
857+
843858
internal void InvokeBehaviourOnNetworkObjectParentChanged(NetworkObject parentNetworkObject)
844859
{
845860
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)

com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ public void Handle(ref NetworkContext context)
6060
}
6161
}
6262

63+
networkObject.InvokeOwnershipChanged(originalOwner, OwnerClientId);
64+
6365
networkManager.NetworkMetrics.TrackOwnershipChangeReceived(context.SenderId, networkObject, context.MessageSize);
6466
}
6567
}

com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId)
257257
throw new SpawnStateException("Object is not spawned");
258258
}
259259

260+
var previous = networkObject.OwnerClientId;
260261
// Assign the new owner
261262
networkObject.OwnerClientId = clientId;
262263

@@ -286,6 +287,12 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId)
286287
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
287288
}
288289
}
290+
291+
// After we have sent the change ownership message to all client observers, invoke the ownership changed notification.
292+
/// !!Important!!
293+
/// This gets called specifically *after* sending the ownership message so any additional messages that need to proceed an ownership
294+
/// change can be sent from NetworkBehaviours that override the <see cref="NetworkBehaviour.OnOwnershipChanged"></see>
295+
networkObject.InvokeOwnershipChanged(previous, clientId);
289296
}
290297

291298
internal bool HasPrefab(NetworkObject.SceneObject sceneObject)

com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,7 @@ public void ParentedNetworkTransformTest([Values] Precision precision, [Values]
588588
success = WaitForConditionOrTimeOutWithTimeTravel(AllChildObjectInstancesHaveChild);
589589
Assert.True(success, "Timed out waiting for all instances to have parented a child!");
590590

591+
TimeTravelToNextTick();
591592
// This validates each child instance has preserved their local space values
592593
AllChildrenLocalTransformValuesMatch(false);
593594

@@ -689,7 +690,7 @@ protected override void OnNewClientCreated(NetworkManager networkManager)
689690

690691
private Precision m_Precision = Precision.Full;
691692
private float m_CurrentHalfPrecision = 0.0f;
692-
private const float k_HalfPrecisionPosScale = 0.03f;
693+
private const float k_HalfPrecisionPosScale = 0.041f;
693694
private const float k_HalfPrecisionRot = 0.725f;
694695

695696
protected override float GetDeltaVarianceThreshold()

0 commit comments

Comments
 (0)