Skip to content

Commit 83752c8

Browse files
fix: NetworkTransform rotation delta check false positives (#1890)
* fix MTT-3370 This fixes the issue with false positive rotation deltas while also assuring the threshold values cannot be set below the minimum threshold value. * test this tests the fix for the rotation delta check and roll over from 0 to 360 degrees. * style * Update CHANGELOG.md * Update CHANGELOG.md * fix Minor adjustment * style fixing merge issue * test fix Making the StopStartRuntimeTests derive from the NetcodeIntegrationTest to avoid the scenario where a specific platform/desktop is running slow for some reason and it take longer than the default NetcodeIntegrationTestHelpers.WaitForClientsConnected's 1 second. Since it now derives from NetcodeIntegrationTest it will also handle cleaning up everything properly in the event the clients truly cannot connect. and not leave behind artifacts in the test runner scene and/or DDOL that would cause all tests that proceeded StopStartRuntimeTests to fail as well. * update removing ThresholdMinimum
1 parent 2151772 commit 83752c8

File tree

4 files changed

+136
-67
lines changed

4 files changed

+136
-67
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ Additional documentation and release notes are available at [Multiplayer Documen
1414
### Removed
1515

1616
### Fixed
17+
18+
- Fixed `NetworkTransform` generating false positive rotation delta checks when rolling over between 0 and 360 degrees. (#1890)
1719
- Fixed client throwing an exception if it has messages in the outbound queue when processing the `NetworkEvent.Disconnect` event and is using UTP. (#1884)
1820
- Fixed issue during client synchronization if 'ValidateSceneBeforeLoading' returned false it would halt the client synchronization process resulting in a client that was approved but not synchronized or fully connected with the server. (#1883)
1921

22+
2023
## [1.0.0-pre.7] - 2022-04-06
2124

2225
### Added
23-
2426
- Added editor only check prior to entering into play mode if the currently open and active scene is in the build list and if not displays a dialog box asking the user if they would like to automatically add it prior to entering into play mode. (#1828)
2527
- Added `UnityTransport` implementation and `com.unity.transport` package dependency (#1823)
2628
- Added `NetworkVariableWritePermission` to `NetworkVariableBase` and implemented `Owner` client writable netvars. (#1762)

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

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ namespace Unity.Netcode.Components
1515
[DefaultExecutionOrder(100000)] // this is needed to catch the update time after the transform was updated by user scripts
1616
public class NetworkTransform : NetworkBehaviour
1717
{
18-
public const float PositionThresholdDefault = .001f;
19-
public const float RotAngleThresholdDefault = .01f;
20-
public const float ScaleThresholdDefault = .01f;
18+
public const float PositionThresholdDefault = 0.001f;
19+
public const float RotAngleThresholdDefault = 0.01f;
20+
public const float ScaleThresholdDefault = 0.01f;
21+
2122
public delegate (Vector3 pos, Quaternion rotOut, Vector3 scale) OnClientRequestChangeDelegate(Vector3 pos, Quaternion rot, Vector3 scale);
2223
public OnClientRequestChangeDelegate OnClientRequestChange;
2324

@@ -249,7 +250,10 @@ public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReade
249250
public bool SyncScaleX = true, SyncScaleY = true, SyncScaleZ = true;
250251

251252
public float PositionThreshold = PositionThresholdDefault;
253+
254+
[Range(0.001f, 360.0f)]
252255
public float RotAngleThreshold = RotAngleThresholdDefault;
256+
253257
public float ScaleThreshold = ScaleThresholdDefault;
254258

255259
/// <summary>
@@ -390,6 +394,16 @@ private void ResetInterpolatedStateToCurrentAuthoritativeState()
390394
m_ScaleZInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleZ, serverTime);
391395
}
392396

397+
/// <summary>
398+
/// Will apply the transform to the LocalAuthoritativeNetworkState and get detailed isDirty information returned.
399+
/// </summary>
400+
/// <param name="transform">transform to apply</param>
401+
/// <returns>bool isDirty, bool isPositionDirty, bool isRotationDirty, bool isScaleDirty</returns>
402+
internal (bool isDirty, bool isPositionDirty, bool isRotationDirty, bool isScaleDirty) ApplyLocalNetworkState(Transform transform)
403+
{
404+
return ApplyTransformToNetworkStateWithInfo(ref m_LocalAuthoritativeNetworkState, m_CachedNetworkManager.LocalTime.Time, transform);
405+
}
406+
393407
// updates `NetworkState` properties if they need to and returns a `bool` indicating whether or not there was any changes made
394408
// returned boolean would be useful to change encapsulating `NetworkVariable<NetworkState>`'s dirty state, e.g. ReplNetworkState.SetDirty(isDirty);
395409
internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkState, double dirtyTime, Transform transformToUse)
@@ -450,23 +464,23 @@ internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkStat
450464
}
451465

452466
if (SyncRotAngleX &&
453-
Mathf.Abs(networkState.RotAngleX - rotAngles.x) > RotAngleThreshold)
467+
Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) > RotAngleThreshold)
454468
{
455469
networkState.RotAngleX = rotAngles.x;
456470
networkState.HasRotAngleX = true;
457471
isRotationDirty = true;
458472
}
459473

460474
if (SyncRotAngleY &&
461-
Mathf.Abs(networkState.RotAngleY - rotAngles.y) > RotAngleThreshold)
475+
Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) > RotAngleThreshold)
462476
{
463477
networkState.RotAngleY = rotAngles.y;
464478
networkState.HasRotAngleY = true;
465479
isRotationDirty = true;
466480
}
467481

468482
if (SyncRotAngleZ &&
469-
Mathf.Abs(networkState.RotAngleZ - rotAngles.z) > RotAngleThreshold)
483+
Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) > RotAngleThreshold)
470484
{
471485
networkState.RotAngleZ = rotAngles.z;
472486
networkState.HasRotAngleZ = true;

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

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ public override void OnNetworkSpawn()
2222

2323
ReadyToReceivePositionUpdate = true;
2424
}
25+
26+
public (bool isDirty, bool isPositionDirty, bool isRotationDirty, bool isScaleDirty) ApplyState()
27+
{
28+
return ApplyLocalNetworkState(transform);
29+
}
2530
}
2631

2732
// [TestFixture(true, true)]
@@ -172,6 +177,80 @@ public IEnumerator TestCantChangeTransformFromOtherSideAuthority([Values] bool t
172177
#endif
173178
}
174179

180+
181+
/// <summary>
182+
/// Validates that rotation checks don't produce false positive
183+
/// results when rolling over between 0 and 360 degrees
184+
/// </summary>
185+
[UnityTest]
186+
public IEnumerator TestRotationThresholdDeltaCheck()
187+
{
188+
// Get the client player's NetworkTransform for both instances
189+
var authoritativeNetworkTransform = m_ServerSideClientPlayer.GetComponent<NetworkTransformTestComponent>();
190+
var otherSideNetworkTransform = m_ClientSideClientPlayer.GetComponent<NetworkTransformTestComponent>();
191+
otherSideNetworkTransform.RotAngleThreshold = authoritativeNetworkTransform.RotAngleThreshold = 5.0f;
192+
193+
var halfThreshold = authoritativeNetworkTransform.RotAngleThreshold * 0.5001f;
194+
var serverRotation = authoritativeNetworkTransform.transform.rotation;
195+
var serverEulerRotation = serverRotation.eulerAngles;
196+
197+
// Verify rotation is not marked dirty when rotated by half of the threshold
198+
serverEulerRotation.y += halfThreshold;
199+
serverRotation.eulerAngles = serverEulerRotation;
200+
authoritativeNetworkTransform.transform.rotation = serverRotation;
201+
var results = authoritativeNetworkTransform.ApplyState();
202+
Assert.IsFalse(results.isRotationDirty, $"Rotation is dirty when rotation threshold is {authoritativeNetworkTransform.RotAngleThreshold} degrees and only adjusted by {halfThreshold} degrees!");
203+
yield return s_DefaultWaitForTick;
204+
205+
// Verify rotation is marked dirty when rotated by another half threshold value
206+
serverEulerRotation.y += halfThreshold;
207+
serverRotation.eulerAngles = serverEulerRotation;
208+
authoritativeNetworkTransform.transform.rotation = serverRotation;
209+
results = authoritativeNetworkTransform.ApplyState();
210+
Assert.IsTrue(results.isRotationDirty, $"Rotation was not dirty when rotated by the threshold value: {authoritativeNetworkTransform.RotAngleThreshold} degrees!");
211+
yield return s_DefaultWaitForTick;
212+
213+
//Reset rotation back to zero on all axis
214+
serverRotation.eulerAngles = serverEulerRotation = Vector3.zero;
215+
authoritativeNetworkTransform.transform.rotation = serverRotation;
216+
yield return s_DefaultWaitForTick;
217+
218+
// Rotate by 360 minus halfThreshold (which is really just negative halfThreshold) and verify rotation is not marked dirty
219+
serverEulerRotation.y = 360 - halfThreshold;
220+
serverRotation.eulerAngles = serverEulerRotation;
221+
authoritativeNetworkTransform.transform.rotation = serverRotation;
222+
results = authoritativeNetworkTransform.ApplyState();
223+
224+
Assert.IsFalse(results.isRotationDirty, $"Rotation is dirty when rotation threshold is {authoritativeNetworkTransform.RotAngleThreshold} degrees and only adjusted by " +
225+
$"{Mathf.DeltaAngle(0, serverEulerRotation.y)} degrees!");
226+
227+
serverEulerRotation.y -= halfThreshold;
228+
serverRotation.eulerAngles = serverEulerRotation;
229+
authoritativeNetworkTransform.transform.rotation = serverRotation;
230+
results = authoritativeNetworkTransform.ApplyState();
231+
232+
Assert.IsTrue(results.isRotationDirty, $"Rotation was not dirty when rotated by {Mathf.DeltaAngle(0, serverEulerRotation.y)} degrees!");
233+
234+
//Reset rotation back to zero on all axis
235+
serverRotation.eulerAngles = serverEulerRotation = Vector3.zero;
236+
authoritativeNetworkTransform.transform.rotation = serverRotation;
237+
yield return s_DefaultWaitForTick;
238+
239+
serverEulerRotation.y -= halfThreshold;
240+
serverRotation.eulerAngles = serverEulerRotation;
241+
authoritativeNetworkTransform.transform.rotation = serverRotation;
242+
results = authoritativeNetworkTransform.ApplyState();
243+
Assert.IsFalse(results.isRotationDirty, $"Rotation is dirty when rotation threshold is {authoritativeNetworkTransform.RotAngleThreshold} degrees and only adjusted by " +
244+
$"{Mathf.DeltaAngle(0, serverEulerRotation.y)} degrees!");
245+
246+
serverEulerRotation.y -= halfThreshold;
247+
serverRotation.eulerAngles = serverEulerRotation;
248+
authoritativeNetworkTransform.transform.rotation = serverRotation;
249+
results = authoritativeNetworkTransform.ApplyState();
250+
251+
Assert.IsTrue(results.isRotationDirty, $"Rotation was not dirty when rotated by {Mathf.DeltaAngle(0, serverEulerRotation.y)} degrees!");
252+
}
253+
175254
/*
176255
* ownership change
177256
* test teleport with interpolation

com.unity.netcode.gameobjects/Tests/Runtime/StopStartRuntimeTests.cs

Lines changed: 34 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -6,69 +6,43 @@
66

77
namespace Unity.Netcode.RuntimeTests
88
{
9-
public class StopStartRuntimeTests
9+
public class StopStartRuntimeTests : NetcodeIntegrationTest
1010
{
11-
[UnityTest]
12-
public IEnumerator WhenShuttingDownAndRestarting_SDKRestartsSuccessfullyAndStaysRunning()
13-
{ // create server and client instances
14-
NetcodeIntegrationTestHelpers.Create(1, out NetworkManager server, out NetworkManager[] clients);
15-
16-
try
17-
{
18-
19-
// create prefab
20-
var gameObject = new GameObject("PlayerObject");
21-
var networkObject = gameObject.AddComponent<NetworkObject>();
22-
networkObject.DontDestroyWithOwner = true;
23-
NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject);
24-
25-
server.NetworkConfig.PlayerPrefab = gameObject;
26-
27-
for (int i = 0; i < clients.Length; i++)
28-
{
29-
clients[i].NetworkConfig.PlayerPrefab = gameObject;
30-
}
31-
32-
// start server and connect clients
33-
NetcodeIntegrationTestHelpers.Start(false, server, clients);
34-
35-
// wait for connection on client side
36-
yield return NetcodeIntegrationTestHelpers.WaitForClientsConnected(clients);
11+
protected override int NumberOfClients => 1;
3712

38-
// wait for connection on server side
39-
yield return NetcodeIntegrationTestHelpers.WaitForClientConnectedToServer(server);
40-
41-
// shutdown the server
42-
server.Shutdown();
43-
44-
// wait 1 frame because shutdowns are delayed
45-
var nextFrameNumber = Time.frameCount + 1;
46-
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
47-
48-
// Verify the shutdown occurred
49-
Assert.IsFalse(server.IsServer);
50-
Assert.IsFalse(server.IsListening);
51-
Assert.IsFalse(server.IsHost);
52-
Assert.IsFalse(server.IsClient);
53-
54-
server.StartServer();
55-
// Verify the server started
56-
Assert.IsTrue(server.IsServer);
57-
Assert.IsTrue(server.IsListening);
58-
59-
// Wait several frames
60-
nextFrameNumber = Time.frameCount + 10;
61-
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
13+
protected override void OnOneTimeSetup()
14+
{
15+
m_UseHost = false;
16+
base.OnOneTimeSetup();
17+
}
6218

63-
// Verify the server is still running
64-
Assert.IsTrue(server.IsServer);
65-
Assert.IsTrue(server.IsListening);
66-
}
67-
finally
68-
{
69-
// cleanup
70-
NetcodeIntegrationTestHelpers.Destroy();
71-
}
19+
[UnityTest]
20+
public IEnumerator WhenShuttingDownAndRestarting_SDKRestartsSuccessfullyAndStaysRunning()
21+
{
22+
// shutdown the server
23+
m_ServerNetworkManager.Shutdown();
24+
25+
// wait 1 frame because shutdowns are delayed
26+
var nextFrameNumber = Time.frameCount + 1;
27+
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
28+
29+
// Verify the shutdown occurred
30+
Assert.IsFalse(m_ServerNetworkManager.IsServer);
31+
Assert.IsFalse(m_ServerNetworkManager.IsListening);
32+
Assert.IsFalse(m_ServerNetworkManager.IsHost);
33+
Assert.IsFalse(m_ServerNetworkManager.IsClient);
34+
35+
m_ServerNetworkManager.StartServer();
36+
// Verify the server started
37+
Assert.IsTrue(m_ServerNetworkManager.IsServer);
38+
Assert.IsTrue(m_ServerNetworkManager.IsListening);
39+
40+
// Wait several frames / one full network tick
41+
yield return s_DefaultWaitForTick;
42+
43+
// Verify the server is still running
44+
Assert.IsTrue(m_ServerNetworkManager.IsServer);
45+
Assert.IsTrue(m_ServerNetworkManager.IsListening);
7246
}
7347
}
7448
}

0 commit comments

Comments
 (0)