com.unity.netcode.gameobjects@1.5.2

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [1.5.2] - 2023-07-24

### Added

### Fixed

- Fixed issue where `NetworkClient.OwnedObjects` was not returning any owned objects due to the `NetworkClient.IsConnected` not being properly set. (#2631)
- Fixed a crash when calling TrySetParent with a null Transform (#2625)
- Fixed issue where a `NetworkTransform` using full precision state updates was losing transform state updates when interpolation was enabled. (#2624)
- Fixed issue where `NetworkObject.SpawnWithObservers` was not being honored for late joining clients. (#2623)
- Fixed issue where invoking `NetworkManager.Shutdown` multiple times, depending upon the timing, could cause an exception. (#2622)
- Fixed issue where removing ownership would not notify the server that it gained ownership. This also resolves the issue where an owner authoritative NetworkTransform would not properly initialize upon removing ownership from a remote client. (#2618)
- Fixed ILPP issues when using CoreCLR and for certain dedicated server builds. (#2614)
- Fixed an ILPP compile error when creating a generic NetworkBehaviour singleton with a static T instance. (#2603)

### Changed
This commit is contained in:
Unity Technologies
2023-07-24 00:00:00 +00:00
parent 4d70c198bd
commit 0581a42b70
24 changed files with 390 additions and 169 deletions

View File

@@ -121,9 +121,7 @@ namespace Unity.Netcode.EditorTests
LogAssert.Expect(LogType.Error, "Invalid network endpoint: 127.0.0.:4242.");
LogAssert.Expect(LogType.Error, "Network listen address (127.0.0.) is Invalid!");
#if UTP_TRANSPORT_2_0_ABOVE
LogAssert.Expect(LogType.Error, "Socket creation failed (error Unity.Baselib.LowLevel.Binding+Baselib_ErrorState: Invalid argument (0x01000003) <argument name stripped>");
#endif
transport.SetConnectionData("127.0.0.1", 4242, "127.0.0.1");
Assert.True(transport.StartServer());

View File

@@ -67,7 +67,6 @@ namespace Unity.Netcode.RuntimeTests
// Set the child object to be inactive in the hierarchy
childObject.SetActive(false);
LogAssert.Expect(LogType.Warning, $"{childObject.name} is disabled! Netcode for GameObjects does not support disabled NetworkBehaviours! The {childBehaviour.GetType().Name} component was skipped during ownership assignment!");
LogAssert.Expect(LogType.Warning, $"{childObject.name} is disabled! Netcode for GameObjects does not support spawning disabled NetworkBehaviours! The {childBehaviour.GetType().Name} component was skipped during spawn!");
parentNetworkObject.Spawn();

View File

@@ -61,8 +61,26 @@ namespace Unity.Netcode.RuntimeTests
return true;
}
/// <summary>
/// Assures the <see cref="ObserverSpawnTests"/> late joining client has all
/// NetworkPrefabs required to connect.
/// </summary>
protected override void OnNewClientCreated(NetworkManager networkManager)
{
foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs)
{
if (!networkManager.NetworkConfig.Prefabs.Contains(networkPrefab.Prefab))
{
networkManager.NetworkConfig.Prefabs.Add(networkPrefab);
}
}
base.OnNewClientCreated(networkManager);
}
/// <summary>
/// This test validates <see cref="NetworkObject.SpawnWithObservers"/> property
/// </summary>
/// <param name="observerTestTypes">whether to spawn with or without observers</param>
[UnityTest]
public IEnumerator ObserverSpawnTests([Values] ObserverTestTypes observerTestTypes)
{
@@ -92,6 +110,23 @@ namespace Unity.Netcode.RuntimeTests
m_ObserverTestType = ObserverTestTypes.WithObservers;
yield return WaitForConditionOrTimeOut(CheckClientsSideObserverTestObj);
AssertOnTimeout($"{k_WithObserversError} {k_ObserverTestObjName} object!");
// Validate that a late joining client does not see the NetworkObject when it spawns
yield return CreateAndStartNewClient();
m_ObserverTestType = ObserverTestTypes.WithoutObservers;
// Just give a little time to make sure nothing spawned
yield return s_DefaultWaitForTick;
yield return WaitForConditionOrTimeOut(CheckClientsSideObserverTestObj);
AssertOnTimeout($"{(withoutObservers ? k_WithoutObserversError : k_WithObserversError)} {k_ObserverTestObjName} object!");
// Now validate that we can make the NetworkObject visible to the newly joined client
m_ObserverTestNetworkObject.NetworkShow(m_ClientNetworkManagers[NumberOfClients].LocalClientId);
// Validate the NetworkObject is visible to all connected clients (including the recently joined client)
m_ObserverTestType = ObserverTestTypes.WithObservers;
yield return WaitForConditionOrTimeOut(CheckClientsSideObserverTestObj);
AssertOnTimeout($"{k_WithObserversError} {k_ObserverTestObjName} object!");
}
}
/// <summary>

View File

@@ -42,6 +42,12 @@ namespace Unity.Netcode.RuntimeTests
public NetworkObjectOwnershipTests(HostOrServer hostOrServer) : base(hostOrServer) { }
public enum OwnershipChecks
{
Change,
Remove
}
protected override void OnServerAndClientsCreated()
{
m_OwnershipPrefab = CreateNetworkObjectPrefab("OnwershipPrefab");
@@ -62,7 +68,7 @@ namespace Unity.Netcode.RuntimeTests
}
[UnityTest]
public IEnumerator TestOwnershipCallbacks()
public IEnumerator TestOwnershipCallbacks([Values] OwnershipChecks ownershipChecks)
{
m_OwnershipObject = SpawnObject(m_OwnershipPrefab, m_ServerNetworkManager);
m_OwnershipNetworkObject = m_OwnershipObject.GetComponent<NetworkObject>();
@@ -109,7 +115,17 @@ namespace Unity.Netcode.RuntimeTests
serverComponent.ResetFlags();
clientComponent.ResetFlags();
serverObject.ChangeOwnership(NetworkManager.ServerClientId);
if (ownershipChecks == OwnershipChecks.Change)
{
// Validates that when ownership is changed back to the server it will get an OnGainedOwnership notification
serverObject.ChangeOwnership(NetworkManager.ServerClientId);
}
else
{
// Validates that when ownership is removed the server gets an OnGainedOwnership notification
serverObject.RemoveOwnership();
}
yield return s_DefaultWaitForTick;
Assert.That(serverComponent.OnGainedOwnershipFired);
@@ -125,7 +141,7 @@ namespace Unity.Netcode.RuntimeTests
/// Verifies that switching ownership between several clients works properly
/// </summary>
[UnityTest]
public IEnumerator TestOwnershipCallbacksSeveralClients()
public IEnumerator TestOwnershipCallbacksSeveralClients([Values] OwnershipChecks ownershipChecks)
{
// Build our message hook entries tables so we can determine if all clients received spawn or ownership messages
var messageHookEntriesForSpawn = new List<MessageHookEntry>();
@@ -247,8 +263,17 @@ namespace Unity.Netcode.RuntimeTests
previousClientComponent = currentClientComponent;
}
// Now change ownership back to the server
serverObject.ChangeOwnership(NetworkManager.ServerClientId);
if (ownershipChecks == OwnershipChecks.Change)
{
// Validates that when ownership is changed back to the server it will get an OnGainedOwnership notification
serverObject.ChangeOwnership(NetworkManager.ServerClientId);
}
else
{
// Validates that when ownership is removed the server gets an OnGainedOwnership notification
serverObject.RemoveOwnership();
}
yield return WaitForConditionOrTimeOut(ownershipMessageHooks);
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for all clients to receive the {nameof(ChangeOwnershipMessage)} message (back to server).");
@@ -269,5 +294,69 @@ namespace Unity.Netcode.RuntimeTests
}
serverComponent.ResetFlags();
}
private const int k_NumberOfSpawnedObjects = 5;
private bool AllClientsHaveCorrectObjectCount()
{
foreach (var clientNetworkManager in m_ClientNetworkManagers)
{
if (clientNetworkManager.LocalClient.OwnedObjects.Count < k_NumberOfSpawnedObjects)
{
return false;
}
}
return true;
}
private bool ServerHasCorrectClientOwnedObjectCount()
{
// Only check when we are the host
if (m_ServerNetworkManager.IsHost)
{
if (m_ServerNetworkManager.LocalClient.OwnedObjects.Count < k_NumberOfSpawnedObjects)
{
return false;
}
}
foreach (var connectedClient in m_ServerNetworkManager.ConnectedClients)
{
if (connectedClient.Value.OwnedObjects.Count < k_NumberOfSpawnedObjects)
{
return false;
}
}
return true;
}
[UnityTest]
public IEnumerator TestOwnedObjectCounts()
{
if (m_ServerNetworkManager.IsHost)
{
for (int i = 0; i < 5; i++)
{
SpawnObject(m_OwnershipPrefab, m_ServerNetworkManager);
}
}
foreach (var clientNetworkManager in m_ClientNetworkManagers)
{
for (int i = 0; i < 5; i++)
{
SpawnObject(m_OwnershipPrefab, clientNetworkManager);
}
}
yield return WaitForConditionOrTimeOut(AllClientsHaveCorrectObjectCount);
AssertOnTimeout($"Not all clients spawned {k_NumberOfSpawnedObjects} {nameof(NetworkObject)}s!");
yield return WaitForConditionOrTimeOut(ServerHasCorrectClientOwnedObjectCount);
AssertOnTimeout($"Server does not have the correct count for all clients spawned {k_NumberOfSpawnedObjects} {nameof(NetworkObject)}s!");
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using Unity.Netcode.Components;
using Unity.Netcode.TestHelpers.Runtime;
@@ -378,7 +379,7 @@ namespace Unity.Netcode.RuntimeTests
{
var success = WaitForConditionOrTimeOutWithTimeTravel(AllInstancesKeptLocalTransformValues);
//TimeTravelToNextTick();
var infoMessage = new System.Text.StringBuilder($"Timed out waiting for all children to have the correct local space values:\n");
var infoMessage = new StringBuilder($"Timed out waiting for all children to have the correct local space values:\n");
var authorityObjectLocalPosition = m_AuthorityChildObject.transform.localPosition;
var authorityObjectLocalRotation = m_AuthorityChildObject.transform.localRotation.eulerAngles;
var authorityObjectLocalScale = m_AuthorityChildObject.transform.localScale;
@@ -567,9 +568,9 @@ namespace Unity.Netcode.RuntimeTests
}
}
// The number of iterations to change position, rotation, and scale for NetworkTransformMultipleChangesOverTime
// Note: this was reduced from 8 iterations to 3 due to the number of tests based on all of the various parameter combinations
// The number of iterations to change position, rotation, and scale for NetworkTransformMultipleChangesOverTime
private const int k_PositionRotationScaleIterations = 3;
private const int k_PositionRotationScaleIterations3Axis = 8;
protected override void OnNewClientCreated(NetworkManager networkManager)
{
@@ -594,22 +595,69 @@ namespace Unity.Netcode.RuntimeTests
private Axis m_CurrentAxis;
private bool m_AxisExcluded;
/// <summary>
/// Randomly determine if an axis should be excluded.
/// If so, then randomly pick one of the axis to be excluded.
/// </summary>
private Vector3 RandomlyExcludeAxis(Vector3 delta)
{
if (Random.Range(0.0f, 1.0f) >= 0.5f)
{
m_AxisExcluded = true;
var axisToIgnore = Random.Range(0, 2);
switch (axisToIgnore)
{
case 0:
{
delta.x = 0;
break;
}
case 1:
{
delta.y = 0;
break;
}
case 2:
{
delta.z = 0;
break;
}
}
}
return delta;
}
/// <summary>
/// This validates that multiple changes can occur within the same tick or over
/// several ticks while still keeping non-authoritative instances synchronized.
/// </summary>
/// <remarks>
/// When testing < 3 axis: Interpolation is disabled and only 3 delta updates are applied per unique test
/// When testing 3 axis: Interpolation is enabled, sometimes an axis is intentionally excluded during a
/// delta update, and it runs through 8 delta updates per unique test.
/// </remarks>
[Test]
public void NetworkTransformMultipleChangesOverTime([Values] TransformSpace testLocalTransform, [Values] OverrideState overideState,
[Values] Precision precision, [Values] Rotation rotationSynch, [Values] Axis axis)
{
// In the name of reducing the very long time it takes to interpolate and run all of the possible combinations,
// we only interpolate when the second client joins
m_AuthoritativeTransform.Interpolate = false;
m_AuthoritativeTransform.InLocalSpace = testLocalTransform == TransformSpace.Local;
bool axisX = axis == Axis.X || axis == Axis.XY || axis == Axis.XZ || axis == Axis.XYZ;
bool axisY = axis == Axis.Y || axis == Axis.XY || axis == Axis.YZ || axis == Axis.XYZ;
bool axisZ = axis == Axis.Z || axis == Axis.XZ || axis == Axis.YZ || axis == Axis.XYZ;
var axisCount = axisX ? 1 : 0;
axisCount += axisY ? 1 : 0;
axisCount += axisZ ? 1 : 0;
// Enable interpolation when all 3 axis are selected to make sure we are synchronizing properly
// when interpolation is enabled.
m_AuthoritativeTransform.Interpolate = axisCount == 3 ? true : false;
m_CurrentAxis = axis;
// Authority dictates what is synchronized and what the precision is going to be
// so we only need to set this on the authoritative side.
m_AuthoritativeTransform.UseHalfFloatPrecision = precision == Precision.Half;
@@ -640,29 +688,49 @@ namespace Unity.Netcode.RuntimeTests
m_AuthoritativeTransform.SyncScaleY = axisY;
m_AuthoritativeTransform.SyncScaleZ = axisZ;
var positionStart = GetRandomVector3(0.25f, 1.75f);
var rotationStart = GetRandomVector3(1f, 15f);
var scaleStart = GetRandomVector3(0.25f, 2.0f);
var position = positionStart;
var rotation = rotationStart;
var scale = scaleStart;
var success = false;
m_AuthoritativeTransform.StatePushed = false;
// Wait for the deltas to be pushed
WaitForConditionOrTimeOutWithTimeTravel(() => m_AuthoritativeTransform.StatePushed);
// Allow the precision settings to propagate first as changing precision
// causes a teleport event to occur
WaitForNextTick();
var iterations = axisCount == 3 ? k_PositionRotationScaleIterations3Axis : k_PositionRotationScaleIterations;
// Move and rotate within the same tick, validate the non-authoritative instance updates
// to each set of changes. Repeat several times.
for (int i = 0; i < k_PositionRotationScaleIterations; i++)
for (int i = 0; i < iterations; i++)
{
// Always reset this per delta update pass
m_AxisExcluded = false;
var deltaPositionDelta = GetRandomVector3(-1.5f, 1.5f);
var deltaRotationDelta = GetRandomVector3(-3.5f, 3.5f);
var deltaScaleDelta = GetRandomVector3(-0.5f, 0.5f);
m_NonAuthoritativeTransform.StateUpdated = false;
m_AuthoritativeTransform.StatePushed = false;
position = positionStart * i;
rotation = rotationStart * i;
scale = scaleStart * i;
// With two or more axis, excluding one of them while chaging another will validate that
// full precision updates are maintaining their target state value(s) to interpolate towards
if (axisCount == 3)
{
position += RandomlyExcludeAxis(deltaPositionDelta);
rotation += RandomlyExcludeAxis(deltaRotationDelta);
scale += RandomlyExcludeAxis(deltaScaleDelta);
}
else
{
position += deltaPositionDelta;
rotation += deltaRotationDelta;
scale += deltaScaleDelta;
}
// Apply delta between ticks
MoveRotateAndScaleAuthority(position, rotation, scale, overideState);
@@ -670,54 +738,37 @@ namespace Unity.Netcode.RuntimeTests
// Wait for the deltas to be pushed
Assert.True(WaitForConditionOrTimeOutWithTimeTravel(() => m_AuthoritativeTransform.StatePushed && m_NonAuthoritativeTransform.StateUpdated), $"[Non-Interpolate {i}] Timed out waiting for state to be pushed ({m_AuthoritativeTransform.StatePushed}) or state to be updated ({m_NonAuthoritativeTransform.StateUpdated})!");
// Wait for deltas to synchronize on non-authoritative side
var success = WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches);
// Provide additional debug info about what failed (if it fails)
if (!success)
// For 3 axis, we will skip validating that the non-authority interpolates to its target point at least once.
// This will validate that non-authoritative updates are maintaining their target state axis values if only 2
// of the axis are being updated to assure interpolation maintains the targeted axial value per axis.
// For 2 and 1 axis tests we always validate per delta update
if (m_AxisExcluded || axisCount < 3)
{
m_EnableVerboseDebug = true;
PositionRotationScaleMatches();
m_EnableVerboseDebug = false;
// Wait for deltas to synchronize on non-authoritative side
success = WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches);
// Provide additional debug info about what failed (if it fails)
if (!success)
{
m_EnableVerboseDebug = true;
success = PositionRotationScaleMatches();
m_EnableVerboseDebug = false;
}
Assert.True(success, $"[Non-Interpolate {i}] Timed out waiting for non-authority to match authority's position or rotation");
}
Assert.True(success, $"[Non-Interpolate {i}] Timed out waiting for non-authority to match authority's position or rotation");
}
// Only enable interpolation when all axis are set (to reduce the test times)
if (axis == Axis.XYZ)
if (axisCount == 3)
{
// Now, enable interpolation
m_AuthoritativeTransform.Interpolate = true;
m_NonAuthoritativeTransform.StateUpdated = false;
m_AuthoritativeTransform.StatePushed = false;
// Wait for the delta (change in interpolation) to be pushed
var success = WaitForConditionOrTimeOutWithTimeTravel(() => m_AuthoritativeTransform.StatePushed && m_NonAuthoritativeTransform.StateUpdated);
Assert.True(success, $"[Interpolation Enable] Timed out waiting for state to be pushed ({m_AuthoritativeTransform.StatePushed}) or state to be updated ({m_NonAuthoritativeTransform.StateUpdated})!");
// Continue for one more update with interpolation enabled
// Note: We are just verifying one update with interpolation enabled due to the number of tests this integration test has to run
// and since the NestedNetworkTransformTests already tests interpolation under the same number of conditions (excluding Axis).
// This is just to verify selecting specific axis doesn't cause issues when interpolating as well.
m_NonAuthoritativeTransform.StateUpdated = false;
m_AuthoritativeTransform.StatePushed = false;
position = positionStart * k_PositionRotationScaleIterations;
rotation = rotationStart * k_PositionRotationScaleIterations;
scale = scaleStart * k_PositionRotationScaleIterations;
MoveRotateAndScaleAuthority(position, rotation, scale, overideState);
// Wait for the deltas to be pushed and updated
success = WaitForConditionOrTimeOutWithTimeTravel(() => m_AuthoritativeTransform.StatePushed && m_NonAuthoritativeTransform.StateUpdated);
Assert.True(success, $"[Interpolation {k_PositionRotationScaleIterations}] Timed out waiting for state to be pushed ({m_AuthoritativeTransform.StatePushed}) or state to be updated ({m_NonAuthoritativeTransform.StateUpdated})!");
success = WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches, 120);
// As a final test, wait for deltas to synchronize on non-authoritative side to assure it interpolates to th
success = WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches);
// Provide additional debug info about what failed (if it fails)
if (!success)
{
m_EnableVerboseDebug = true;
PositionRotationScaleMatches();
success = PositionRotationScaleMatches();
m_EnableVerboseDebug = false;
}
Assert.True(success, $"[Interpolation {k_PositionRotationScaleIterations}] Timed out waiting for non-authority to match authority's position or rotation");
Assert.True(success, $"Timed out waiting for non-authority to match authority's position or rotation");
}
}