com.unity.netcode.gameobjects@2.2.0

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).

## [2.2.0] - 2024-12-12

### Added

- Added `NetworkObject.OwnershipStatus.SessionOwner` to allow Network Objects to be distributable and only owned by the Session Owner. This flag will override all other `OwnershipStatus` flags. (#3175)
- Added `UnityTransport.GetEndpoint` method to provide a way to obtain `NetworkEndpoint` information of a connection via client identifier. (#3130)
- Added `NetworkTransport.OnEarlyUpdate` and `NetworkTransport.OnPostLateUpdate` methods to provide more control over handling transport related events at the start and end of each frame. (#3113)

### Fixed

- Fixed issue where the server, host, or session owner would not populate the in-scene place `NetworkObject` table if the scene was loaded prior to starting the `NetworkManager`. (#3177)
- Fixed issue where the `NetworkObjectIdHash` value could be incorrect when entering play mode while still in prefab edit mode with pending changes and using MPPM. (#3162)
- Fixed issue where a sever only `NetworkManager` instance would spawn the actual `NetworkPrefab`'s `GameObject` as opposed to creating an instance of it. (#3160)
- Fixed issue where only the session owner (as opposed to all clients) would handle spawning prefab overrides properly when using a distributed authority network topology. (#3160)
- Fixed issue where an exception was thrown when calling `NetworkManager.Shutdown` after calling `UnityTransport.Shutdown`. (#3118)
- Fixed issue where `NetworkList` properties on in-scene placed `NetworkObject`s could cause small memory leaks when entering playmode. (#3147)
- Fixed in-scene `NertworkObject` synchronization issue when loading a scene with currently connected clients connected to a session created by a `NetworkManager` started as a server (i.e. not as a host). (#3133)
- Fixed issue where a `NetworkManager` started as a server would not add itself as an observer to in-scene placed `NetworkObject`s instantiated and spawned by a scene loading event. (#3133)
- Fixed issue where spawning a player using `NetworkObject.InstantiateAndSpawn` or `NetworkSpawnManager.InstantiateAndSpawn` would not update the `NetworkSpawnManager.PlayerObjects` or assign the newly spawned player to the `NetworkClient.PlayerObject`. (#3122)
- Fixed issue where queued UnitTransport (NetworkTransport) message batches were being sent on the next frame. They are now sent at the end of the frame during `PostLateUpdate`.  (#3113)
- Fixed issue where `NotOwnerRpcTarget` or `OwnerRpcTarget` were not using their replacements `NotAuthorityRpcTarget` and `AuthorityRpcTarget` which would invoke a warning. (#3111)
- Fixed issue where client is removed as an observer from spawned objects when their player instance is despawned. (#3110)
- Fixed issue where `NetworkAnimator` would statically allocate write buffer space for `Animator` parameters that could cause a write error if the number of parameters exceeded the space allocated. (#3108)

### Changed

- In-scene placed `NetworkObject`s have been made distributable when balancing object distribution after a connection event. (#3175)
- Optimised `NetworkVariable` and `NetworkTransform` related packets when in Distributed Authority mode.
- The Debug Simulator section of the Unity Transport component was removed. This section was not functional anymore and users are now recommended to use the more featureful [Network Simulator](https://docs-multiplayer.unity3d.com/tools/current/tools-network-simulator/) tool from the Multiplayer Tools package instead. (#3121)
This commit is contained in:
Unity Technologies
2024-12-12 00:00:00 +00:00
parent 016788c21e
commit 8fe07bbad2
78 changed files with 2243 additions and 1187 deletions

View File

@@ -205,13 +205,14 @@ namespace Unity.Netcode.RuntimeTests
[UnityTest]
public IEnumerator NetworkVariableDelta()
{
var component = Client.LocalClient.PlayerObject.GetComponent<TestNetworkComponent>();
var message = new NetworkVariableDeltaMessage
{
NetworkObjectId = 0,
NetworkBehaviourIndex = 1,
DeliveryMappedNetworkVariableIndex = new HashSet<int> { 2, 3, 4 },
NetworkObjectId = Client.LocalClient.PlayerObject.NetworkObjectId,
NetworkBehaviourIndex = component.NetworkBehaviourId,
DeliveryMappedNetworkVariableIndex = new HashSet<int> { 0, 1 },
TargetClientId = 5,
NetworkBehaviour = Client.LocalClient.PlayerObject.GetComponent<TestNetworkComponent>(),
NetworkBehaviour = component,
};
yield return SendMessage(ref message);
@@ -569,7 +570,7 @@ namespace Unity.Netcode.RuntimeTests
private IEnumerator SendMessage<T>(ref T message) where T : INetworkMessage
{
Client.MessageManager.SetVersion(k_ClientId, XXHash.Hash32(typeof(T).FullName), 0);
Client.MessageManager.SetVersion(k_ClientId, XXHash.Hash32(typeof(T).FullName), message.Version);
var clientIds = new NativeArray<ulong>(1, Allocator.Temp);
clientIds[0] = k_ClientId;
@@ -625,7 +626,8 @@ namespace Unity.Netcode.RuntimeTests
}
var writer = new FastBufferWriter(1024, Allocator.Temp);
message.Serialize(writer, 0);
// Serialize the message using the known message version
message.Serialize(writer, message.Version);
var testName = TestContext.CurrentContext.Test.Name;
if (!m_ExpectedMessages.ContainsKey(testName))

View File

@@ -216,6 +216,21 @@ namespace Unity.Netcode.RuntimeTests
m_ErrorLogLevel2.Append($"[Client-{client.LocalClientId} Prefab Mismatch][Expected GlobalObjectIdHash: {expectedGlobalObjectIdHash} but was {remoteNetworkClient.PlayerObject.GlobalObjectIdHash}]");
success = false;
}
var foundPlayer = false;
foreach (var playerObject in client.SpawnManager.PlayerObjects)
{
if (playerObject.NetworkObjectId == clientToValidate.LocalClient.PlayerObject.NetworkObjectId)
{
foundPlayer = true;
break;
}
}
if (!foundPlayer)
{
m_ErrorLogLevel1.AppendLine($"[Client-{client.LocalClientId}] Local {nameof(NetworkSpawnManager.PlayerObjects)} does not contain {clientToValidate.LocalClient.PlayerObject.name}!");
success = false;
}
}
return success;
}
@@ -231,6 +246,12 @@ namespace Unity.Netcode.RuntimeTests
m_ErrorLogLevel1.AppendLine($"[Client-{m_ServerNetworkManager.LocalClientId}]{m_ErrorLogLevel2}");
success = false;
}
if (m_ServerNetworkManager.LocalClient.PlayerObject == null)
{
m_ErrorLogLevel1.AppendLine($"[Client-{m_ServerNetworkManager.LocalClientId}] Local {nameof(NetworkClient.PlayerObject)} is null!");
success = false;
}
}
foreach (var client in m_ClientNetworkManagers)
@@ -240,6 +261,17 @@ namespace Unity.Netcode.RuntimeTests
m_ErrorLogLevel1.AppendLine($"[Client-{client.LocalClientId}]{m_ErrorLogLevel2}");
success = false;
}
if (client.LocalClient.PlayerObject == null)
{
m_ErrorLogLevel1.AppendLine($"[Client-{client.LocalClientId}] Local {nameof(NetworkClient.PlayerObject)} is null!");
success = false;
}
else
if (!client.SpawnManager.PlayerObjects.Contains(client.LocalClient.PlayerObject))
{
m_ErrorLogLevel1.AppendLine($"[Client-{client.LocalClientId}] Local {nameof(NetworkSpawnManager.PlayerObjects)} does not contain {client.LocalClient.PlayerObject.name}!");
success = false;
}
}
return success;
@@ -250,12 +282,19 @@ namespace Unity.Netcode.RuntimeTests
return m_PlayerPrefabs[Random.Range(0, m_PlayerPrefabs.Count() - 1)].GetComponent<NetworkObject>();
}
public enum PlayerSpawnTypes
{
Normal,
NetworkObject,
SpawnManager
}
/// <summary>
/// Validates that when a client changes their player object that all connected client instances mirror the
/// client's new player object.
/// </summary>
[UnityTest]
public IEnumerator ValidatePlayerObjects()
public IEnumerator ValidatePlayerObjects([Values] PlayerSpawnTypes playerSpawnType)
{
// Just do a quick validation for all connected client's NetworkClients
yield return WaitForConditionOrTimeOut(AllNetworkClientsValidated);
@@ -268,14 +307,51 @@ namespace Unity.Netcode.RuntimeTests
if (m_UseHost)
{
playerPrefabToSpawn = GetRandomPlayerPrefab();
playerInstance = SpawnPlayerObject(playerPrefabToSpawn.gameObject, m_ServerNetworkManager);
switch (playerSpawnType)
{
case PlayerSpawnTypes.Normal:
{
playerInstance = SpawnPlayerObject(playerPrefabToSpawn.gameObject, m_ServerNetworkManager);
break;
}
case PlayerSpawnTypes.NetworkObject:
{
playerInstance = NetworkObject.InstantiateAndSpawn(playerPrefabToSpawn.gameObject, m_ServerNetworkManager, isPlayerObject: true).gameObject;
break;
}
case PlayerSpawnTypes.SpawnManager:
{
playerInstance = m_ServerNetworkManager.SpawnManager.InstantiateAndSpawn(playerPrefabToSpawn, isPlayerObject: true).gameObject;
break;
}
}
m_ChangedPlayerPrefabs.Add(m_ServerNetworkManager.LocalClientId, playerPrefabToSpawn.GlobalObjectIdHash);
}
foreach (var client in m_ClientNetworkManagers)
{
playerPrefabToSpawn = GetRandomPlayerPrefab();
playerInstance = SpawnPlayerObject(playerPrefabToSpawn.gameObject, client);
var networkManager = m_DistributedAuthority ? client : m_ServerNetworkManager;
switch (playerSpawnType)
{
case PlayerSpawnTypes.Normal:
{
playerInstance = SpawnPlayerObject(playerPrefabToSpawn.gameObject, client);
break;
}
case PlayerSpawnTypes.NetworkObject:
{
playerInstance = NetworkObject.InstantiateAndSpawn(playerPrefabToSpawn.gameObject, networkManager, client.LocalClientId, isPlayerObject: true).gameObject;
break;
}
case PlayerSpawnTypes.SpawnManager:
{
playerInstance = networkManager.SpawnManager.InstantiateAndSpawn(playerPrefabToSpawn, client.LocalClientId, isPlayerObject: true).gameObject;
break;
}
}
m_ChangedPlayerPrefabs.Add(client.LocalClientId, playerPrefabToSpawn.GlobalObjectIdHash);
}

View File

@@ -128,7 +128,7 @@ namespace Unity.Netcode.RuntimeTests
yield return WaitForConditionOrTimeOut(ValidateObjectSpawnedOnAllClients);
AssertOnTimeout($"[Failed To Spawn] {firstInstance.name}: \n {m_ErrorLog}");
// Validate the base non-assigned persmissions value for all instances are the same.
// Validate the base non-assigned permissions value for all instances are the same.
yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients);
AssertOnTimeout($"[Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}");
@@ -141,9 +141,15 @@ namespace Unity.Netcode.RuntimeTests
foreach (var permissionObject in Enum.GetValues(typeof(NetworkObject.OwnershipStatus)))
{
var permission = (NetworkObject.OwnershipStatus)permissionObject;
// Adding the SessionOwner flag here should fail as this NetworkObject is not owned by the Session Owner
if (permission == NetworkObject.OwnershipStatus.SessionOwner)
{
Assert.IsFalse(firstInstance.SetOwnershipStatus(permission), $"[Add][IncorrectPermissions] Setting {NetworkObject.OwnershipStatus.SessionOwner} is not valid when the client is not the Session Owner: \n {m_ErrorLog}!");
continue;
}
// Add the status
firstInstance.SetOwnershipStatus(permission);
// Validate the persmissions value for all instances are the same.
// Validate the permissions value for all instances are the same.
yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients);
AssertOnTimeout($"[Add][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}");
@@ -153,7 +159,7 @@ namespace Unity.Netcode.RuntimeTests
continue;
}
firstInstance.RemoveOwnershipStatus(permission);
// Validate the persmissions value for all instances are the same.
// Validate the permissions value for all instances are the same.
yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients);
AssertOnTimeout($"[Remove][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}");
}
@@ -163,7 +169,7 @@ namespace Unity.Netcode.RuntimeTests
firstInstance.SetOwnershipStatus(multipleFlags, true);
Assert.IsTrue(firstInstance.HasOwnershipStatus(multipleFlags), $"[Set][Multi-flag Failure] Expected: {(ushort)multipleFlags} but was {(ushort)firstInstance.Ownership}!");
// Validate the persmissions value for all instances are the same.
// Validate the permissions value for all instances are the same.
yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients);
AssertOnTimeout($"[Set Multiple][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}");
@@ -175,7 +181,7 @@ namespace Unity.Netcode.RuntimeTests
// Validate that the Distributable flag is still set
Assert.IsTrue(firstInstance.HasOwnershipStatus(NetworkObject.OwnershipStatus.Distributable), $"[Remove][Multi-flag Failure] Expected: {(ushort)NetworkObject.OwnershipStatus.Distributable} but was {(ushort)firstInstance.Ownership}!");
// Validate the persmissions value for all instances are the same.
// Validate the permissions value for all instances are the same.
yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients);
AssertOnTimeout($"[Set Multiple][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}");
@@ -186,7 +192,7 @@ namespace Unity.Netcode.RuntimeTests
// Clear the flags, set the permissions to transferrable, and lock ownership in one pass.
firstInstance.SetOwnershipStatus(NetworkObject.OwnershipStatus.Transferable, true, NetworkObject.OwnershipLockActions.SetAndLock);
// Validate the persmissions value for all instances are the same.
// Validate the permissions value for all instances are the same.
yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients);
AssertOnTimeout($"[Reset][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}");
@@ -199,7 +205,7 @@ namespace Unity.Netcode.RuntimeTests
$" status is {secondInstanceHelper.OwnershipPermissionsFailureStatus}!");
firstInstance.SetOwnershipLock(false);
// Validate the persmissions value for all instances are the same.
// Validate the permissions value for all instances are the same.
yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients);
AssertOnTimeout($"[Unlock][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}");
@@ -208,7 +214,7 @@ namespace Unity.Netcode.RuntimeTests
// Now try to acquire ownership
secondInstance.ChangeOwnership(m_ClientNetworkManagers[1].LocalClientId);
// Validate the persmissions value for all instances are the same
// Validate the permissions value for all instances are the same
yield return WaitForConditionOrTimeOut(() => secondInstance.IsOwner);
AssertOnTimeout($"[Acquire Ownership Failed] Client-{m_ClientNetworkManagers[1].LocalClientId} failed to get ownership!");
@@ -220,7 +226,7 @@ namespace Unity.Netcode.RuntimeTests
// Clear the flags, set the permissions to RequestRequired, and lock ownership in one pass.
secondInstance.SetOwnershipStatus(NetworkObject.OwnershipStatus.RequestRequired, true);
// Validate the persmissions value for all instances are the same.
// Validate the permissions value for all instances are the same.
yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients);
AssertOnTimeout($"[Unlock][Permissions Mismatch] {secondInstance.name}: \n {m_ErrorLog}");
@@ -238,7 +244,7 @@ namespace Unity.Netcode.RuntimeTests
// Start with a request for the client we expect to be given ownership
var requestStatus = firstInstance.RequestOwnership();
Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{firstInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!");
Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{firstInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!");
// Get the 3rd client to send a request at the "relatively" same time
var thirdInstance = m_ClientNetworkManagers[2].SpawnManager.SpawnedObjects[networkObjectId];
@@ -248,7 +254,7 @@ namespace Unity.Netcode.RuntimeTests
requestStatus = thirdInstance.RequestOwnership();
// We expect the 3rd client's request should be able to be sent at this time as well (i.e. creates the race condition between two clients)
Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{m_ServerNetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!");
Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{m_ServerNetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!");
// We expect the first requesting client to be given ownership
yield return WaitForConditionOrTimeOut(() => firstInstance.IsOwner);
@@ -263,7 +269,7 @@ namespace Unity.Netcode.RuntimeTests
yield return WaitForConditionOrTimeOut(() => thirdInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.RequestInProgress);
AssertOnTimeout($"[Request In Progress Failed] Client-{thirdInstanceHelper.NetworkManager.LocalClientId} did not get the right request denied reponse!");
// Validate the persmissions value for all instances are the same.
// Validate the permissions value for all instances are the same.
yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients);
AssertOnTimeout($"[Unlock][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}");
@@ -278,11 +284,11 @@ namespace Unity.Netcode.RuntimeTests
// Send out a request from three clients at the same time
// The first one sent (and received for this test) gets ownership
requestStatus = secondInstance.RequestOwnership();
Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{secondInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!");
Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{secondInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!");
requestStatus = thirdInstance.RequestOwnership();
Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{thirdInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!");
Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{thirdInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!");
requestStatus = fourthInstance.RequestOwnership();
Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{fourthInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!");
Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{fourthInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!");
// The 2nd and 3rd client should be denied and the 4th client should be approved
yield return WaitForConditionOrTimeOut(() =>
@@ -296,7 +302,7 @@ namespace Unity.Netcode.RuntimeTests
yield return WaitForConditionOrTimeOut(() => ValidateAllInstancesAreOwnedByClient(secondInstance.NetworkManager.LocalClientId));
AssertOnTimeout($"[Ownership Mismatch] {secondInstance.name}: \n {m_ErrorLog}");
// Validate the persmissions value for all instances are the same.
// Validate the permissions value for all instances are the same.
yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients);
AssertOnTimeout($"[Unlock][Permissions Mismatch] {secondInstance.name}: \n {m_ErrorLog}");
@@ -314,22 +320,84 @@ namespace Unity.Netcode.RuntimeTests
// Send out a request from all three clients
requestStatus = firstInstance.RequestOwnership();
Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{firstInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!");
Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{firstInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!");
requestStatus = thirdInstance.RequestOwnership();
Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{thirdInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!");
Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{thirdInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!");
requestStatus = fourthInstance.RequestOwnership();
Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{fourthInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!");
Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{fourthInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!");
requestStatus = daHostInstance.RequestOwnership();
Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{daHostInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!");
Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{daHostInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!");
// The server and the 2nd client should be denied and the third client should be approved
// Only the client marked as ClientToAllowOwnership (daHost) should be approved. All others should be denied.
yield return WaitForConditionOrTimeOut(() =>
(firstInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.Denied) &&
(thirdInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.Denied) &&
(fourthInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.Denied) &&
(daHostInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.Approved)
);
AssertOnTimeout($"[Targeted Owner] Client-{daHostInstance.NetworkManager.LocalClientId} did not get the right request reponse: {daHostInstanceHelper.OwnershipRequestResponseStatus} Expecting: {NetworkObject.OwnershipRequestResponseStatus.Approved}!");
AssertOnTimeout($"[Targeted Owner] Client-{daHostInstance.NetworkManager.LocalClientId} did not get the right request response: {daHostInstanceHelper.OwnershipRequestResponseStatus} Expecting: {NetworkObject.OwnershipRequestResponseStatus.Approved}!");
///////////////////////////////////////////////
// Test OwnershipStatus.SessionOwner:
///////////////////////////////////////////////
OwnershipPermissionsTestHelper.CurrentOwnedInstance = daHostInstance;
m_ObjectToValidate = OwnershipPermissionsTestHelper.CurrentOwnedInstance;
// Add multiple statuses
daHostInstance.SetOwnershipStatus(NetworkObject.OwnershipStatus.Transferable | NetworkObject.OwnershipStatus.SessionOwner);
// Validate the permissions value for all instances are the same.
yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients);
AssertOnTimeout($"[Add][Permissions Mismatch] {daHostInstance.name}: \n {m_ErrorLog}");
// Trying to set SessionOwner flag should override any other flags.
Assert.IsFalse(daHostInstance.HasOwnershipStatus(NetworkObject.OwnershipStatus.Transferable), $"[Set][SessionOwner flag Failure] Expected: {NetworkObject.OwnershipStatus.Transferable} not to be set!");
// Add another status. Should fail as SessionOwner should be exclusive
daHostInstance.SetOwnershipStatus(NetworkObject.OwnershipStatus.Distributable);
Assert.IsFalse(daHostInstance.HasOwnershipStatus(NetworkObject.OwnershipStatus.Distributable), $"[Add][SessionOwner flag Failure] Expected: {NetworkObject.OwnershipStatus.Transferable} not to be set!");
// Request ownership of the SessionOwner flag instance
requestStatus = firstInstance.RequestOwnership();
Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestRequiredNotSet, $"Client-{firstInstance.NetworkManager.LocalClientId} should not be able to send a request for ownership because object is marked as owned by the session owner. {requestStatus}!");
// Set ownership directly on local object. This will allow the request to be sent
firstInstance.Ownership = NetworkObject.OwnershipStatus.RequestRequired;
requestStatus = firstInstance.RequestOwnership();
Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{firstInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!");
// Request should be denied with CannotRequest
yield return WaitForConditionOrTimeOut(() => firstInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.CannotRequest);
AssertOnTimeout($"[Targeted Owner] Client-{firstInstance.NetworkManager.LocalClientId} did not get the right request response: {daHostInstanceHelper.OwnershipRequestResponseStatus} Expecting: {NetworkObject.OwnershipRequestResponseStatus.CannotRequest}!");
// Try changing the ownership explicitly
// Get the cloned daHostInstance instance on a client side
var clientInstance = m_ClientNetworkManagers[2].SpawnManager.SpawnedObjects[daHostInstance.NetworkObjectId];
// Get the client instance of the OwnershipPermissionsTestHelper component
var clientInstanceHelper = clientInstance.GetComponent<OwnershipPermissionsTestHelper>();
// Have the client attempt to change ownership
clientInstance.ChangeOwnership(m_ClientNetworkManagers[2].LocalClientId);
// Verify the client side gets a permission failure status of NetworkObject.OwnershipPermissionsFailureStatus.SessionOwnerOnly
Assert.IsTrue(clientInstanceHelper.OwnershipPermissionsFailureStatus == NetworkObject.OwnershipPermissionsFailureStatus.SessionOwnerOnly,
$"Expected {clientInstance.name} to return {NetworkObject.OwnershipPermissionsFailureStatus.SessionOwnerOnly} but its permission failure" +
$" status is {clientInstanceHelper.OwnershipPermissionsFailureStatus}!");
// Have the session owner attempt to change ownership to a non-session owner
daHostInstance.ChangeOwnership(m_ClientNetworkManagers[2].LocalClientId);
// Verify the session owner cannot assign a SessionOwner permission NetworkObject to a non-sessionowner client
Assert.IsTrue(daHostInstanceHelper.OwnershipPermissionsFailureStatus == NetworkObject.OwnershipPermissionsFailureStatus.SessionOwnerOnly,
$"Expected {daHostInstance.name} to return {NetworkObject.OwnershipPermissionsFailureStatus.SessionOwnerOnly} but its permission failure" +
$" status is {daHostInstanceHelper.OwnershipPermissionsFailureStatus}!");
// Remove status
daHostInstance.RemoveOwnershipStatus(NetworkObject.OwnershipStatus.SessionOwner);
// Validate the permissions value for all instances are the same.
yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients);
AssertOnTimeout($"[Remove][Permissions Mismatch] {daHostInstance.name}: \n {m_ErrorLog}");
}
internal class OwnershipPermissionsTestHelper : NetworkBehaviour

View File

@@ -0,0 +1,106 @@
using System.Collections;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
internal class SessionVersionConnectionRequest : NetcodeIntegrationTest
{
protected override int NumberOfClients => 0;
public SessionVersionConnectionRequest() : base(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost) { }
private bool m_UseValidSessionVersion;
private bool m_ClientWasDisconnected;
private NetworkManager m_ClientNetworkManager;
/// <summary>
/// Callback used to mock the scenario where a client has an invalid session version
/// </summary>
/// <returns><see cref="SessionConfig"/></returns>
private SessionConfig GetInavlidSessionConfig()
{
return new SessionConfig(m_ServerNetworkManager.SessionConfig.SessionVersion - 1);
}
/// <summary>
/// Overriding this method allows us to configure the newly instantiated client's
/// NetworkManager prior to it being started.
/// </summary>
/// <param name="networkManager">the newly instantiated NetworkManager</param>
protected override void OnNewClientCreated(NetworkManager networkManager)
{
m_ClientWasDisconnected = false;
m_ClientNetworkManager = networkManager;
m_ClientNetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback;
if (!m_UseValidSessionVersion)
{
networkManager.OnGetSessionConfig = GetInavlidSessionConfig;
}
base.OnNewClientCreated(networkManager);
}
/// <summary>
/// Tracks if the client was disconnected or not
/// </summary>
private void OnClientDisconnectCallback(ulong clientId)
{
m_ClientWasDisconnected = true;
m_ClientNetworkManager.OnClientDisconnectCallback -= OnClientDisconnectCallback;
}
/// <summary>
/// This handles disabling the internal integration test logic that waits for
/// clients to be connected. When we know the client will be disconnected,
/// we want to have the NetcodeIntegrationTest not wait for the client to
/// connect (otherwise it will timeout there and fail the test).
/// </summary>
/// <param name="networkManager"></param>
/// <returns>true = wait | false = don't wait</returns>
protected override bool ShouldWaitForNewClientToConnect(NetworkManager networkManager)
{
return m_UseValidSessionVersion;
}
/// <summary>
/// Validates that when the client's session config version is valid a client will be
/// allowed to connect and when it is not valid the client will be disconnected.
/// </summary>
/// <remarks>
/// This is just a mock of the service logic to validate everything on the NGO side is
/// working correctly.
/// </remarks>
/// <param name="useValidSessionVersion">true = use valid session version | false = use invalid session version</param>
[UnityTest]
public IEnumerator ValidateSessionVersion([Values] bool useValidSessionVersion)
{
// Test client being disconnected due to invalid session version
m_UseValidSessionVersion = useValidSessionVersion;
yield return CreateAndStartNewClient();
yield return s_DefaultWaitForTick;
if (!m_UseValidSessionVersion)
{
yield return WaitForConditionOrTimeOut(() => m_ClientWasDisconnected);
AssertOnTimeout("Client was not disconnected when it should have been!");
Assert.True(m_ClientNetworkManager.DisconnectReason == ConnectionRequestMessage.InvalidSessionVersionMessage, "Client did not receive the correct invalid session version message!");
}
else
{
Assert.False(m_ClientWasDisconnected, "Client was disconnected when it was expected to connect!");
Assert.True(m_ClientNetworkManager.IsConnectedClient, "Client did not connect properly using the correct session version!");
}
}
/// <summary>
/// Invoked at the end of each integration test pass.
/// Primarily used to clean up for the next pass.
/// </summary>
protected override IEnumerator OnTearDown()
{
m_ClientNetworkManager.OnClientDisconnectCallback -= OnClientDisconnectCallback;
m_ClientNetworkManager = null;
yield return base.OnTearDown();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 62913c78d28137141acc47ea8f63e657

View File

@@ -1,6 +1,9 @@
using System;
using System.Collections;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using Unity.Netcode.Transports.UTP;
using Unity.Networking.Transport;
using UnityEngine;
using UnityEngine.TestTools;
@@ -157,4 +160,49 @@ namespace Unity.Netcode.RuntimeTests
}
}
}
/// <summary>
/// Verifies the UnityTransport.GetEndpoint method returns
/// valid NetworkEndpoint information.
/// </summary>
internal class TransportEndpointTests : NetcodeIntegrationTest
{
protected override int NumberOfClients => 2;
[UnityTest]
public IEnumerator GetEndpointReportedCorrectly()
{
var serverUnityTransport = m_ServerNetworkManager.NetworkConfig.NetworkTransport as UnityTransport;
var serverEndpoint = new NetworkEndpoint();
var clientEndpoint = new NetworkEndpoint();
foreach (var client in m_ClientNetworkManagers)
{
var unityTransport = client.NetworkConfig.NetworkTransport as UnityTransport;
serverEndpoint = unityTransport.GetEndpoint(m_ServerNetworkManager.LocalClientId);
clientEndpoint = serverUnityTransport.GetEndpoint(client.LocalClientId);
Assert.IsTrue(serverEndpoint.IsValid);
Assert.IsTrue(clientEndpoint.IsValid);
Assert.IsTrue(clientEndpoint.Address.Split(":")[0] == unityTransport.ConnectionData.Address);
Assert.IsTrue(serverEndpoint.Address.Split(":")[0] == serverUnityTransport.ConnectionData.Address);
Assert.IsTrue(serverEndpoint.Port == unityTransport.ConnectionData.Port);
Assert.IsTrue(clientEndpoint.Port >= serverUnityTransport.ConnectionData.Port);
}
// Now validate that when disconnected it returns a non-valid NetworkEndPoint
var clientId = m_ClientNetworkManagers[0].LocalClientId;
m_ClientNetworkManagers[0].Shutdown();
yield return s_DefaultWaitForTick;
serverEndpoint = (m_ClientNetworkManagers[0].NetworkConfig.NetworkTransport as UnityTransport).GetEndpoint(m_ServerNetworkManager.LocalClientId);
clientEndpoint = serverUnityTransport.GetEndpoint(clientId);
Assert.IsFalse(serverEndpoint.IsValid);
Assert.IsFalse(clientEndpoint.IsValid);
// Validate that invalid client identifiers return an invalid NetworkEndPoint
serverEndpoint = (m_ClientNetworkManagers[0].NetworkConfig.NetworkTransport as UnityTransport).GetEndpoint((ulong)UnityEngine.Random.Range(NumberOfClients + 1, 30));
clientEndpoint = serverUnityTransport.GetEndpoint((ulong)UnityEngine.Random.Range(NumberOfClients + 1, 30));
Assert.IsFalse(serverEndpoint.IsValid);
Assert.IsFalse(clientEndpoint.IsValid);
}
}
}

View File

@@ -127,7 +127,11 @@ namespace Unity.Netcode.RuntimeTests
}
// Verifies that removing the ownership when the default (server) is already set does not cause a Key Not Found Exception
m_ServerNetworkManager.SpawnManager.RemoveOwnership(m_OwnershipNetworkObject);
// Distributed authority does not allow remove ownership (users are instructed to use change ownership)
if (!m_DistributedAuthority)
{
m_ServerNetworkManager.SpawnManager.RemoveOwnership(m_OwnershipNetworkObject);
}
var serverObject = m_ServerNetworkManager.SpawnManager.SpawnedObjects[ownershipNetworkObjectId];
var clientObject = m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[ownershipNetworkObjectId];
@@ -237,7 +241,12 @@ namespace Unity.Netcode.RuntimeTests
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for all clients to change ownership!");
// Verifies that removing the ownership when the default (server) is already set does not cause a Key Not Found Exception
m_ServerNetworkManager.SpawnManager.RemoveOwnership(m_OwnershipNetworkObject);
// Distributed authority does not allow remove ownership (users are instructed to use change ownership)
if (!m_DistributedAuthority)
{
m_ServerNetworkManager.SpawnManager.RemoveOwnership(m_OwnershipNetworkObject);
}
var serverObject = m_ServerNetworkManager.SpawnManager.SpawnedObjects[ownershipNetworkObjectId];
Assert.That(serverObject, Is.Not.Null);
var clientObject = (NetworkObject)null;

View File

@@ -36,13 +36,25 @@ namespace Unity.Netcode.RuntimeTests
{
VerifyObjectIsSpawnedOnClient.ResetObjectTable();
m_ClientNetworkTransformPrefab = CreateNetworkObjectPrefab("OwnerAuthorityTest");
var clientNetworkObject = m_ClientNetworkTransformPrefab.GetComponent<NetworkObject>();
// When running in distributed authority mode, make the NetworkObject transferable
clientNetworkObject.SetOwnershipStatus(m_DistributedAuthority ? NetworkObject.OwnershipStatus.Transferable : NetworkObject.OwnershipStatus.None);
var clientNetworkTransform = m_ClientNetworkTransformPrefab.AddComponent<TestClientNetworkTransform>();
clientNetworkTransform.AuthorityMode = NetworkTransform.AuthorityModes.Owner;
clientNetworkTransform.Interpolate = false;
clientNetworkTransform.UseHalfFloatPrecision = false;
var rigidBody = m_ClientNetworkTransformPrefab.AddComponent<Rigidbody>();
rigidBody.useGravity = false;
rigidBody.maxDepenetrationVelocity = 0;
rigidBody.mass = 100;
rigidBody.linearDamping = 100;
rigidBody.interpolation = RigidbodyInterpolation.None;
rigidBody.maxLinearVelocity = 0;
rigidBody.detectCollisions = false;
rigidBody.position = Vector3.zero;
rigidBody.rotation = Quaternion.identity;
rigidBody.transform.position = Vector3.zero;
rigidBody.transform.rotation = Quaternion.identity;
// NOTE: We don't use a sphere collider for this integration test because by the time we can
// assure they don't collide and skew the results the NetworkObjects are already synchronized
// with skewed results
@@ -51,11 +63,22 @@ namespace Unity.Netcode.RuntimeTests
m_ClientNetworkTransformPrefab.AddComponent<VerifyObjectIsSpawnedOnClient>();
m_NetworkTransformPrefab = CreateNetworkObjectPrefab("ServerAuthorityTest");
var networkTransform = m_NetworkTransformPrefab.AddComponent<NetworkTransform>();
var networkObject = m_ClientNetworkTransformPrefab.GetComponent<NetworkObject>();
// When running in distributed authority mode, make the NetworkObject transferable
networkObject.SetOwnershipStatus(m_DistributedAuthority ? NetworkObject.OwnershipStatus.Transferable : NetworkObject.OwnershipStatus.None);
var networkTransform = m_NetworkTransformPrefab.AddComponent<TestClientNetworkTransform>();
rigidBody = m_NetworkTransformPrefab.AddComponent<Rigidbody>();
rigidBody.useGravity = false;
rigidBody.maxDepenetrationVelocity = 0;
rigidBody.linearDamping = 100;
rigidBody.mass = 100;
rigidBody.interpolation = RigidbodyInterpolation.None;
rigidBody.maxLinearVelocity = 0;
rigidBody.detectCollisions = false;
rigidBody.position = Vector3.zero;
rigidBody.rotation = Quaternion.identity;
rigidBody.transform.position = Vector3.zero;
rigidBody.transform.rotation = Quaternion.identity;
// NOTE: We don't use a sphere collider for this integration test because by the time we can
// assure they don't collide and skew the results the NetworkObjects are already synchronized
// with skewed results
@@ -276,10 +299,12 @@ namespace Unity.Netcode.RuntimeTests
};
if (m_MotionModel == MotionModels.UseRigidbody)
{
TestClientNetworkTransform.EnableLogState(m_EnableVerboseDebug);
var ownerRigidbody = ownerInstance.GetComponent<Rigidbody>();
ownerRigidbody.Move(valueSetByOwner, rotation);
yield return new WaitForFixedUpdate();
ownerRigidbody.linearVelocity = Vector3.zero;
yield return s_DefaultWaitForTick;
yield return new WaitForFixedUpdate();
ownerInstance.transform.localScale = valueSetByOwner;
}
else
@@ -312,15 +337,22 @@ namespace Unity.Netcode.RuntimeTests
VerifyObjectIsSpawnedOnClient.ResetObjectTable();
if (m_DistributedAuthority)
{
ownerInstance.NetworkObject.ChangeOwnership(networkManagerNonOwner.LocalClientId);
Assert.True(nonOwnerInstance.OwnerClientId != networkManagerNonOwner.LocalClientId, $"Non-Owner Client-{networkManagerNonOwner.LocalClientId} was already the owner prior to changing ownership!");
nonOwnerInstance.NetworkObject.ChangeOwnership(networkManagerNonOwner.LocalClientId);
nonOwnerInstance.GetComponent<Rigidbody>().linearVelocity = Vector3.zero;
Assert.True(nonOwnerInstance.OwnerClientId == networkManagerNonOwner.LocalClientId, $"Client-{networkManagerNonOwner.LocalClientId} failed to change ownership!");
LogNonOwnerRigidBody(3);
yield return WaitForConditionOrTimeOut(() => ownerInstance.GetComponent<NetworkObject>().OwnerClientId == networkManagerNonOwner.LocalClientId);
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for original owner {networkManagerOwner.name}'s object instance {nonOwnerInstance.name} to change ownership!");
}
else
{
m_ServerNetworkManager.SpawnManager.ChangeOwnership(serverSideInstance.GetComponent<NetworkObject>(), networkManagerNonOwner.LocalClientId, true);
LogNonOwnerRigidBody(3);
yield return WaitForConditionOrTimeOut(() => nonOwnerInstance.GetComponent<NetworkObject>().OwnerClientId == networkManagerNonOwner.LocalClientId);
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change ownership!");
}
LogNonOwnerRigidBody(3);
yield return WaitForConditionOrTimeOut(() => nonOwnerInstance.GetComponent<NetworkObject>().OwnerClientId == networkManagerNonOwner.LocalClientId);
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change ownership!");
LogNonOwnerRigidBody(4);
// Re-assign the ownership references and wait for the non-owner instance to be notified of ownership change
@@ -331,6 +363,7 @@ namespace Unity.Netcode.RuntimeTests
yield return WaitForConditionOrTimeOut(() => VerifyObjectIsSpawnedOnClient.GetClientInstance(networkManagerNonOwner.LocalClientId) != null);
nonOwnerInstance = VerifyObjectIsSpawnedOnClient.GetClientInstance(networkManagerNonOwner.LocalClientId);
Assert.NotNull(nonOwnerInstance);
Assert.True(!nonOwnerInstance.IsOwner, $"Ownership failed to change on Client-{networkManagerNonOwner.LocalClientId} side! Expected owner to be {networkManagerOwner.LocalClientId} but owner is still {networkManagerNonOwner.LocalClientId}!");
// Make sure the owner is not kinematic and the non-owner(s) are kinematic
Assert.False(ownerInstance.GetComponent<Rigidbody>().isKinematic, $"{networkManagerOwner.name}'s object instance {ownerInstance.name} is kinematic when it should not be!");
@@ -344,7 +377,7 @@ namespace Unity.Netcode.RuntimeTests
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change its transform!\n" +
$"Expected Position: {valueSetByOwner} | Current Position: {transformToTest.position}\n" +
$"Expected Rotation: {valueSetByOwner} | Current Rotation: {transformToTest.rotation.eulerAngles}\n" +
$"Expected Scale: {valueSetByOwner} | Current Scale: {transformToTest.localScale}");
$"Expected Scale: {valueSetByOwner} | Current Scale: {transformToTest.localScale}\n {nonOwnerInstance.GetComponent<TestClientNetworkTransform>().LogInfoBuilder}");
LogNonOwnerRigidBody(5);
// Have the new owner change transform values and wait for those values to be applied on the non-owner side.
@@ -358,6 +391,7 @@ namespace Unity.Netcode.RuntimeTests
var ownerRigidbody = ownerInstance.GetComponent<Rigidbody>();
ownerRigidbody.Move(valueSetByOwner, rotation);
LogOwnerRigidBody(2);
yield return new WaitForFixedUpdate();
ownerInstance.GetComponent<NetworkTransform>().LogMotion = m_EnableVerboseDebug;
nonOwnerInstance.GetComponent<NetworkTransform>().LogMotion = m_EnableVerboseDebug;
ownerRigidbody.linearVelocity = Vector3.zero;
@@ -377,10 +411,11 @@ namespace Unity.Netcode.RuntimeTests
LogOwnerRigidBody(4);
LogNonOwnerRigidBody(7);
}
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change its transform!\n" +
$"Expected Position: {valueSetByOwner} | Current Position: {transformToTest.position}\n" +
$"Expected Rotation: {valueSetByOwner} | Current Rotation: {transformToTest.rotation.eulerAngles}\n" +
$"Expected Scale: {valueSetByOwner} | Current Scale: {transformToTest.localScale}");
$"Expected Scale: {valueSetByOwner} | Current Scale: {transformToTest.localScale}\n {nonOwnerInstance.GetComponent<TestClientNetworkTransform>().LogInfoBuilder}");
// The last check is to verify non-owners cannot change transform values after ownership has changed
nonOwnerInstance.transform.position = Vector3.zero;
@@ -445,6 +480,12 @@ namespace Unity.Netcode.RuntimeTests
Assert.True(nonOwnerInstance.transform.position == valueSetByOwner, $"{m_ClientNetworkManagers[0].name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {Vector3.one} Is Currently:{nonOwnerInstance.transform.position}");
}
protected override IEnumerator OnTearDown()
{
TestClientNetworkTransform.EnableLogState(false);
return base.OnTearDown();
}
/// <summary>
/// NetworkTransformOwnershipTests helper behaviour
/// </summary>
@@ -457,6 +498,10 @@ namespace Unity.Netcode.RuntimeTests
NetworkManagerRelativeSpawnedObjects.Clear();
}
/// <summary>
/// For testing, just before changing ownership the table is cleared to assure that
/// ownership tansfer occurs. This will add the new owner to the table.
/// </summary>
public override void OnGainedOwnership()
{
if (!NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId))
@@ -466,6 +511,10 @@ namespace Unity.Netcode.RuntimeTests
base.OnGainedOwnership();
}
/// <summary>
/// For testing, just before changing ownership the table is cleared to assure that
/// ownership tansfer occurs. This will add the previous owner to the table.
/// </summary>
public override void OnLostOwnership()
{
if (!NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId))
@@ -515,28 +564,57 @@ namespace Unity.Netcode.RuntimeTests
[DisallowMultipleComponent]
internal class TestClientNetworkTransform : NetworkTransform
{
//public override void OnNetworkSpawn()
//{
// base.OnNetworkSpawn();
// CanCommitToTransform = IsOwner;
//}
//protected override void Update()
//{
// CanCommitToTransform = IsOwner;
// base.Update();
// if (NetworkManager.Singleton != null && (NetworkManager.Singleton.IsConnectedClient || NetworkManager.Singleton.IsListening))
// {
// if (CanCommitToTransform)
// {
// TryCommitTransformToServer(transform, NetworkManager.LocalTime.Time);
// }
// }
//}
protected override bool OnIsServerAuthoritative()
public static void EnableLogState(bool enable)
{
return false;
s_LogStateEnabled = enable;
TrackByStateId = enable;
}
private static bool s_LogStateEnabled;
internal StringBuilder LogInfoBuilder = new StringBuilder();
private void LogInfo(NetworkTransformState state)
{
if (s_LogStateEnabled)
{
LogInfoBuilder.AppendLine($"N:{name} | CID:{NetworkManager.LocalClientId} | SID: {state.StateId} | NT:{NetworkManager.ServerTime.Tick} | Pos: {transform.position} | Sc: {transform.localScale}");
}
}
protected override void OnOwnershipChanged(ulong previous, ulong current)
{
if (s_LogStateEnabled)
{
LogInfoBuilder.AppendLine($"Ownership Changed: {previous} --> {current} | Position: {transform.position}");
}
LogInfo(LocalAuthoritativeNetworkState);
base.OnOwnershipChanged(previous, current);
// Assure no velocity is set on this object for this particular test
if (current == NetworkManager.LocalClientId)
{
GetComponent<Rigidbody>().linearVelocity = Vector3.zero;
}
}
protected override void OnAuthorityPushTransformState(ref NetworkTransformState networkTransformState)
{
LogInfo(networkTransformState);
base.OnAuthorityPushTransformState(ref networkTransformState);
}
protected override void OnBeforeUpdateTransformState()
{
LogInfo(LocalAuthoritativeNetworkState);
base.OnBeforeUpdateTransformState();
}
protected override void OnNetworkTransformStateUpdated(ref NetworkTransformState oldState, ref NetworkTransformState newState)
{
LogInfo(newState);
base.OnNetworkTransformStateUpdated(ref oldState, ref newState);
}
}
}
@@ -791,7 +869,7 @@ namespace Unity.Netcode.RuntimeTests
foreach (var networkManager in m_NetworkManagers)
{
// Randomize the position
// Randomize the position
RandomizeObjectTransformPositions(m_SpawnObject);
// Create an instance owned by the specified networkmanager

View File

@@ -123,7 +123,7 @@ namespace Unity.Netcode.RuntimeTests
}
/// <summary>
/// This test validates the SwitchTransformSpaceWhenParented setting under all network topologies
/// This test validates the SwitchTransformSpaceWhenParented setting under all network topologies
/// </summary>
[Test]
public void SwitchTransformSpaceWhenParentedTest([Values(0.5f, 1.0f, 5.0f)] float scale)

View File

@@ -20,7 +20,7 @@ namespace Unity.Netcode.RuntimeTests
/// - HashSet
/// This also does some testing on nested collections, but does
/// not test every possible combination.
/// </summary>
/// </summary>
[TestFixture(HostOrServer.Host)]
[TestFixture(HostOrServer.Server)]
public class NetworkVariableCollectionsTests : NetcodeIntegrationTest
@@ -218,7 +218,7 @@ namespace Unity.Netcode.RuntimeTests
yield return WaitForConditionOrTimeOut(() => compInt.CompareTrackedChanges(ListTestHelperBase.Targets.Owner));
AssertOnTimeout($"Client-{client.LocalClientId} full set failed to synchronize on {nameof(ListTestHelperInt)} {compInt.name}!");
//////////////////////////////////
// Server Full Set
// Server Full Set
compIntServer.FullSet(GetRandomIntList(5), ListTestHelperBase.Targets.Server);
yield return WaitForConditionOrTimeOut(() => compIntServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server));
AssertOnTimeout($"Server full set failed to synchronize on {nameof(ListTestHelperInt)} {compIntServer.name}!");
@@ -285,7 +285,7 @@ namespace Unity.Netcode.RuntimeTests
// Only test restore on non-host clients (otherwise a host is both server and client/owner)
if (!client.IsServer)
{
// No Write Client Remove List<int> item with CheckDirtyState restore
// No Write Client Remove List<int> item with CheckDirtyState restore
compListInt.Remove(compListInt.ListCollectionServer.Value[index], ListTestHelperBase.Targets.Server);
yield return WaitForConditionOrTimeOut(() => compListInt.CompareTrackedChanges(ListTestHelperBase.Targets.Server));
AssertOnTimeout($"Client-{client.LocalClientId} remove failed to restore on {nameof(ListTestHelperListInt)} {compListIntServer.name}! {compListIntServer.GetLog()}");
@@ -474,7 +474,7 @@ namespace Unity.Netcode.RuntimeTests
yield return WaitForConditionOrTimeOut(() => compObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server));
AssertOnTimeout($"Client-{client.LocalClientId} change failed to restore on {nameof(ListTestHelperSerializableObject)} {compObjectServer.name}!");
// No Write Client Remove Serializable item with owner state update restore
// No Write Client Remove Serializable item with owner state update restore
compObject.ListCollectionServer.Value[index] = SerializableObject.GetRandomObject();
}
compObjectServer.ListCollectionServer.Value[index] = SerializableObject.GetRandomObject();
@@ -838,7 +838,7 @@ namespace Unity.Netcode.RuntimeTests
compDictionaryServer.ListCollectionOwner.IsDirty();
yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Owner));
AssertOnTimeout($"Server add to owner write collection property failed to restore on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}");
// Server-side add the same key and SerializableObject to owner write permission (would throw key exists exception too if previous failed)
// Server-side add the same key and SerializableObject to owner write permission (would throw key exists exception too if previous failed)
compDictionaryServer.ListCollectionOwner.Value.Add(newEntry.Item1, newEntry.Item2);
// Server-side add a completely new key and SerializableObject to to owner write permission property
compDictionaryServer.ListCollectionOwner.Value.Add(GetNextKey(), SerializableObject.GetRandomObject());
@@ -864,7 +864,7 @@ namespace Unity.Netcode.RuntimeTests
compDictionary.ListCollectionServer.IsDirty();
yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Server));
AssertOnTimeout($"Client-{client.LocalClientId} add to server write collection property failed to restore on {className} {compDictionary.name}! {compDictionary.GetLog()}");
// Client-side add the same key and SerializableObject to server write permission property (would throw key exists exception too if previous failed)
// Client-side add the same key and SerializableObject to server write permission property (would throw key exists exception too if previous failed)
compDictionary.ListCollectionServer.Value.Add(newEntry.Item1, newEntry.Item2);
// Client-side add a completely new key and SerializableObject to to server write permission property
compDictionary.ListCollectionServer.Value.Add(GetNextKey(), SerializableObject.GetRandomObject());

View File

@@ -1012,8 +1012,7 @@ namespace Unity.Netcode.RuntimeTests
}
[Test]
// Exceptions should be thrown in DA mode with UserNetworkVariableSerialization
public void TestUnsupportedManagedTypesWithUserSerializationDoNotThrowExceptionsInClientServerMode()
public void TestUnsupportedManagedTypesWithUserSerializationDoNotThrowExceptions()
{
var variable = new NetworkVariable<string>();
UserNetworkVariableSerialization<string>.ReadValue = (FastBufferReader reader, out string value) =>
@@ -1039,10 +1038,6 @@ namespace Unity.Netcode.RuntimeTests
variable.ReadField(reader);
Assert.AreEqual("012345", variable.Value);
}
catch (Exception)
{
Assert.True(NetworkVariableSerialization<UserNetworkVariableSerialization<string>>.IsDistributedAuthority);
}
finally
{
UserNetworkVariableSerialization<string>.ReadValue = null;

View File

@@ -1,4 +1,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
@@ -184,4 +187,150 @@ namespace Unity.Netcode.RuntimeTests
}
}
}
/// <summary>
/// This test validates that when a player is spawned it has all observers
/// properly set and the spawned and player object lists are properly populated.
/// It also validates that when a player is despawned and the client is still connected
/// that the client maintains its observers for other players.
/// </summary>
[TestFixture(HostOrServer.DAHost)]
[TestFixture(HostOrServer.Host)]
[TestFixture(HostOrServer.Server)]
internal class PlayerSpawnAndDespawnTests : NetcodeIntegrationTest
{
protected override int NumberOfClients => 4;
private StringBuilder m_ErrorLog = new StringBuilder();
public PlayerSpawnAndDespawnTests(HostOrServer hostOrServer) : base(hostOrServer) { }
private bool ValidateObservers(ulong playerClientId, NetworkObject player, ref List<NetworkManager> networkManagers)
{
foreach (var networkManager in networkManagers)
{
if (player != null && player.IsSpawned)
{
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(player.NetworkObjectId))
{
m_ErrorLog.AppendLine($"Client-{networkManager.LocalClientId} does not contain a spawned object entry {nameof(NetworkObject)}-{player.NetworkObjectId} for Client-{playerClientId}!");
return false;
}
var playerClone = networkManager.SpawnManager.SpawnedObjects[player.NetworkObjectId];
foreach (var clientId in networkManager.ConnectedClientsIds)
{
if (!playerClone.IsNetworkVisibleTo(clientId))
{
m_ErrorLog.AppendLine($"Client-{networkManager.LocalClientId} failed visibility check for Client-{clientId} on {nameof(NetworkObject)}-{player.NetworkObjectId} for Client-{playerClientId}!");
return false;
}
}
var foundPlayerClone = (NetworkObject)null;
foreach (var playerObject in networkManager.SpawnManager.PlayerObjects)
{
if (playerObject.OwnerClientId == playerClientId && playerObject.NetworkObjectId == player.NetworkObjectId)
{
foundPlayerClone = playerObject;
break;
}
}
if (!foundPlayerClone)
{
m_ErrorLog.AppendLine($"Client-{networkManager.LocalClientId} does not contain a player entry for {nameof(NetworkObject)}-{player.NetworkObjectId} for Client-{playerClientId}!");
return false;
}
}
else
{
// If the player client in question is despawned, then no NetworkManager instance
// should contain a clone of that (or the client's NetworkManager instance as well)
foreach (var playerClone in networkManager.SpawnManager.PlayerObjects)
{
if (playerClone.OwnerClientId == playerClientId)
{
m_ErrorLog.AppendLine($"Client-{networkManager.LocalClientId} contains a player {nameof(NetworkObject)}-{playerClone.NetworkObjectId} for Client-{playerClientId} when it should be despawned!");
return false;
}
}
}
}
return true;
}
private bool ValidateAllClientPlayerObservers()
{
var networkManagers = new List<NetworkManager>();
if (m_ServerNetworkManager.IsHost)
{
networkManagers.Add(m_ServerNetworkManager);
}
foreach (var networkManager in m_ClientNetworkManagers)
{
networkManagers.Add(networkManager);
}
m_ErrorLog.Clear();
var success = true;
foreach (var networkManager in networkManagers)
{
var spawnedOrNot = networkManager.LocalClient.PlayerObject == null ? "despawned" : "spawned";
m_ErrorLog.AppendLine($"Validating Client-{networkManager.LocalClientId} {spawnedOrNot} player.");
if (networkManager.LocalClient == null)
{
m_ErrorLog.AppendLine($"No {nameof(NetworkClient)} found for Client-{networkManager.LocalClientId}!");
success = false;
break;
}
if (!ValidateObservers(networkManager.LocalClientId, networkManager.LocalClient.PlayerObject, ref networkManagers))
{
m_ErrorLog.AppendLine($"Client-{networkManager.LocalClientId} validation pass failed.");
success = false;
break;
}
}
networkManagers.Clear();
return success;
}
[UnityTest]
public IEnumerator PlayerSpawnDespawn()
{
// Validate all observers are properly set with all players spawned
yield return WaitForConditionOrTimeOut(ValidateAllClientPlayerObservers);
AssertOnTimeout($"First Validation Failed:\n {m_ErrorLog}");
var selectedClient = m_ClientNetworkManagers[Random.Range(0, m_ClientNetworkManagers.Count() - 1)];
var playerSelected = selectedClient.LocalClient.PlayerObject;
if (m_DistributedAuthority)
{
playerSelected.Despawn(false);
}
else
{
m_ServerNetworkManager.SpawnManager.SpawnedObjects[playerSelected.NetworkObjectId].Despawn(true);
}
// Validate all observers are properly set with one of the players despawned
yield return WaitForConditionOrTimeOut(ValidateAllClientPlayerObservers);
AssertOnTimeout($"Second Validation Failed:\n {m_ErrorLog}");
if (m_DistributedAuthority)
{
playerSelected.SpawnAsPlayerObject(selectedClient.LocalClientId, false);
}
else
{
SpawnPlayerObject(m_ServerNetworkManager.NetworkConfig.PlayerPrefab, selectedClient);
}
// Validate all observers are properly set when the client's player is respawned.
yield return WaitForConditionOrTimeOut(ValidateAllClientPlayerObservers);
AssertOnTimeout($"Third Validation Failed:\n {m_ErrorLog}");
}
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 83133d71f2112db45ba626157c46e7f8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -85,9 +85,14 @@ namespace Unity.Netcode.RuntimeTests
private const string k_PrefabObjectName = "NetworkPrefabHandlerTestObject";
[Test]
public void NetworkPrefabHandlerClass()
public void NetworkPrefabHandlerClass([Values] bool distributedAuthority)
{
Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _));
var networkConfig = new NetworkConfig()
{
NetworkTopology = distributedAuthority ? NetworkTopologyTypes.DistributedAuthority : NetworkTopologyTypes.ClientServer,
};
Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _, networkConfig: networkConfig));
var testPrefabObjectName = k_PrefabObjectName;
Guid baseObjectID = NetworkManagerHelper.AddGameNetworkObject(testPrefabObjectName);

View File

@@ -0,0 +1,268 @@
using System.Collections;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
/// <summary>
/// Integration test that validates spawning instances of <see cref="NetworkPrefab"/>s with overrides and
/// <see cref="NetworkPrefabHandler"/> registered overrides.
/// </summary>
[TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Server)]
[TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)]
[TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)]
internal class NetworkPrefabOverrideTests : NetcodeIntegrationTest
{
private const string k_PrefabRootName = "PrefabObj";
protected override int NumberOfClients => 2;
private NetworkPrefab m_ClientSidePlayerPrefab;
private NetworkPrefab m_PrefabOverride;
public NetworkPrefabOverrideTests(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { }
/// <summary>
/// Prefab override handler that will instantiate the ServerSideInstance (m_PlayerPrefab) only on server instances
/// and will spawn the ClientSideInstance (m_ClientSidePlayerPrefab.Prefab) only on clients and/or a host.
/// </summary>
public class TestPrefabOverrideHandler : MonoBehaviour, INetworkPrefabInstanceHandler
{
public GameObject ServerSideInstance;
public GameObject ClientSideInstance;
private NetworkManager m_NetworkManager;
private void Start()
{
m_NetworkManager = GetComponent<NetworkManager>();
m_NetworkManager.PrefabHandler.AddHandler(ServerSideInstance, this);
}
private void OnDestroy()
{
if (m_NetworkManager != null && m_NetworkManager.PrefabHandler != null)
{
m_NetworkManager.PrefabHandler.RemoveHandler(ServerSideInstance);
}
}
public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation)
{
var instance = m_NetworkManager.IsClient ? Instantiate(ClientSideInstance) : Instantiate(ServerSideInstance);
return instance.GetComponent<NetworkObject>();
}
public void Destroy(NetworkObject networkObject)
{
Object.Destroy(networkObject);
}
}
/// <summary>
/// Mock component for testing that the client-side player is using the right
/// network prefab.
/// </summary>
public class ClientSideOnlyComponent : MonoBehaviour
{
}
/// <summary>
/// When we create the player prefab, make a modified instance that will be used
/// with the <see cref="TestPrefabOverrideHandler"/>.
/// </summary>
protected override void OnCreatePlayerPrefab()
{
var clientPlayer = Object.Instantiate(m_PlayerPrefab);
clientPlayer.AddComponent<ClientSideOnlyComponent>();
Object.DontDestroyOnLoad(clientPlayer);
m_ClientSidePlayerPrefab = new NetworkPrefab()
{
Prefab = clientPlayer,
};
base.OnCreatePlayerPrefab();
}
/// <summary>
/// Add the additional <see cref="NetworkPrefab"/>s and <see cref="TestPrefabOverrideHandler"/>s to
/// all <see cref="NetworkManager"/> instances.
/// </summary>
protected override void OnServerAndClientsCreated()
{
// Create a NetworkPrefab with an override
var basePrefab = NetcodeIntegrationTestHelpers.CreateNetworkObject($"{k_PrefabRootName}-base", m_ServerNetworkManager, true);
var targetPrefab = NetcodeIntegrationTestHelpers.CreateNetworkObject($"{k_PrefabRootName}-over", m_ServerNetworkManager, true);
m_PrefabOverride = new NetworkPrefab()
{
Prefab = basePrefab,
Override = NetworkPrefabOverride.Prefab,
SourcePrefabToOverride = basePrefab,
OverridingTargetPrefab = targetPrefab,
};
// Add the prefab override handler for instance specific player prefabs to the server side
var playerPrefabOverrideHandler = m_ServerNetworkManager.gameObject.AddComponent<TestPrefabOverrideHandler>();
playerPrefabOverrideHandler.ServerSideInstance = m_PlayerPrefab;
playerPrefabOverrideHandler.ClientSideInstance = m_ClientSidePlayerPrefab.Prefab;
// Add the NetworkPrefab with override
m_ServerNetworkManager.NetworkConfig.Prefabs.Add(m_PrefabOverride);
// Add the client player prefab that will be used on clients (and the host)
m_ServerNetworkManager.NetworkConfig.Prefabs.Add(m_ClientSidePlayerPrefab);
foreach (var networkManager in m_ClientNetworkManagers)
{
// Add the prefab override handler for instance specific player prefabs to the client side
playerPrefabOverrideHandler = networkManager.gameObject.AddComponent<TestPrefabOverrideHandler>();
playerPrefabOverrideHandler.ServerSideInstance = m_PlayerPrefab;
playerPrefabOverrideHandler.ClientSideInstance = m_ClientSidePlayerPrefab.Prefab;
// Add the NetworkPrefab with override
networkManager.NetworkConfig.Prefabs.Add(m_PrefabOverride);
// Add the client player prefab that will be used on clients (and the host)
networkManager.NetworkConfig.Prefabs.Add(m_ClientSidePlayerPrefab);
}
m_PrefabOverride.Prefab.GetComponent<NetworkObject>().IsSceneObject = false;
m_PrefabOverride.SourcePrefabToOverride.GetComponent<NetworkObject>().IsSceneObject = false;
m_PrefabOverride.OverridingTargetPrefab.GetComponent<NetworkObject>().IsSceneObject = false;
m_ClientSidePlayerPrefab.Prefab.GetComponent<NetworkObject>().IsSceneObject = false;
base.OnServerAndClientsCreated();
}
protected override IEnumerator OnTearDown()
{
if (m_PrefabOverride != null)
{
if (m_PrefabOverride.SourcePrefabToOverride)
{
Object.Destroy(m_PrefabOverride.SourcePrefabToOverride);
}
if (m_PrefabOverride.OverridingTargetPrefab)
{
Object.Destroy(m_PrefabOverride.OverridingTargetPrefab);
}
}
if (m_ClientSidePlayerPrefab != null)
{
if (m_ClientSidePlayerPrefab.Prefab)
{
Object.Destroy(m_ClientSidePlayerPrefab.Prefab);
}
}
m_ClientSidePlayerPrefab = null;
m_PrefabOverride = null;
yield return base.OnTearDown();
}
private GameObject GetPlayerNetworkPrefabObject(NetworkManager networkManager)
{
return networkManager.IsClient ? m_ClientSidePlayerPrefab.Prefab : m_PlayerPrefab;
}
[UnityTest]
public IEnumerator PrefabOverrideTests()
{
var prefabNetworkObject = (NetworkObject)null;
var spawnedGlobalObjectId = (uint)0;
var networkManagers = m_ClientNetworkManagers.ToList();
if (m_UseHost)
{
networkManagers.Insert(0, m_ServerNetworkManager);
}
else
{
// If running as just a server, validate that all player prefab clone instances are the server side version
prefabNetworkObject = GetPlayerNetworkPrefabObject(m_ServerNetworkManager).GetComponent<NetworkObject>();
foreach (var playerEntry in m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId])
{
spawnedGlobalObjectId = playerEntry.Value.GlobalObjectIdHash;
Assert.IsTrue(prefabNetworkObject.GlobalObjectIdHash == spawnedGlobalObjectId, $"Server-Side {playerEntry.Value.name} was spawned as prefab ({spawnedGlobalObjectId}) but we expected ({prefabNetworkObject.GlobalObjectIdHash})!");
}
}
// Validates prefab overrides via the NetworkPrefabHandler.
// Validate the player prefab instance clones relative to all NetworkManagers.
foreach (var networkManager in networkManagers)
{
// Get the expected player prefab to be spawned based on the NetworkManager
prefabNetworkObject = GetPlayerNetworkPrefabObject(networkManager).GetComponent<NetworkObject>();
if (networkManager.IsClient)
{
spawnedGlobalObjectId = networkManager.LocalClient.PlayerObject.GlobalObjectIdHash;
Assert.IsTrue(prefabNetworkObject.GlobalObjectIdHash == spawnedGlobalObjectId, $"{networkManager.name} spawned player prefab ({spawnedGlobalObjectId}) did not match the expected one ({prefabNetworkObject.GlobalObjectIdHash})!");
}
foreach (var playerEntry in m_PlayerNetworkObjects[networkManager.LocalClientId])
{
// We already checked our locally spawned player prefab above
if (playerEntry.Key == networkManager.LocalClientId)
{
continue;
}
spawnedGlobalObjectId = playerEntry.Value.GlobalObjectIdHash;
Assert.IsTrue(prefabNetworkObject.GlobalObjectIdHash == spawnedGlobalObjectId, $"Client-{networkManager.LocalClientId} clone of {playerEntry.Value.name} was spawned as prefab ({spawnedGlobalObjectId}) but we expected ({prefabNetworkObject.GlobalObjectIdHash})!");
}
}
// Validates prefab overrides via NetworkPrefab configuration.
var spawnedInstance = (NetworkObject)null;
var networkManagerOwner = m_ServerNetworkManager;
if (m_DistributedAuthority)
{
networkManagerOwner = m_ClientNetworkManagers[0];
}
// Clients and Host will spawn the OverridingTargetPrefab while a dedicated server will spawn the SourcePrefabToOverride
var expectedServerGlobalObjectIdHash = networkManagerOwner.IsClient ? m_PrefabOverride.OverridingTargetPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash : m_PrefabOverride.SourcePrefabToOverride.GetComponent<NetworkObject>().GlobalObjectIdHash;
var expectedClientGlobalObjectIdHash = m_PrefabOverride.OverridingTargetPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
spawnedInstance = NetworkObject.InstantiateAndSpawn(m_PrefabOverride.SourcePrefabToOverride, networkManagerOwner, networkManagerOwner.LocalClientId);
var builder = new StringBuilder();
bool ObjectSpawnedOnAllNetworkMangers()
{
builder.Clear();
if (!m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(spawnedInstance.NetworkObjectId))
{
builder.AppendLine($"Client-{m_ServerNetworkManager.LocalClientId} failed to spawn {spawnedInstance.name}-{spawnedInstance.NetworkObjectId}!");
return false;
}
var instanceGID = m_ServerNetworkManager.SpawnManager.SpawnedObjects[spawnedInstance.NetworkObjectId].GlobalObjectIdHash;
if (instanceGID != expectedServerGlobalObjectIdHash)
{
builder.AppendLine($"Client-{m_ServerNetworkManager.LocalClientId} instance {spawnedInstance.name}-{spawnedInstance.NetworkObjectId} GID is {instanceGID} but was expected to be {expectedServerGlobalObjectIdHash}!");
return false;
}
foreach (var networkManger in m_ClientNetworkManagers)
{
if (!networkManger.SpawnManager.SpawnedObjects.ContainsKey(spawnedInstance.NetworkObjectId))
{
builder.AppendLine($"Client-{networkManger.LocalClientId} failed to spawn {spawnedInstance.name}-{spawnedInstance.NetworkObjectId}!");
return false;
}
instanceGID = networkManger.SpawnManager.SpawnedObjects[spawnedInstance.NetworkObjectId].GlobalObjectIdHash;
if (instanceGID != expectedClientGlobalObjectIdHash)
{
builder.AppendLine($"Client-{networkManger.LocalClientId} instance {spawnedInstance.name}-{spawnedInstance.NetworkObjectId} GID is {instanceGID} but was expected to be {expectedClientGlobalObjectIdHash}!");
return false;
}
}
return true;
}
yield return WaitForConditionOrTimeOut(ObjectSpawnedOnAllNetworkMangers);
AssertOnTimeout($"The spawned prefab override validation failed!\n {builder}");
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c5e27e24bc5f783458d6a45585308f87

View File

@@ -33,7 +33,7 @@ namespace Unity.Netcode.RuntimeTests
if (transport)
{
transport.Shutdown();
UnityEngine.Object.DestroyImmediate(transport);
UnityEngine.Object.DestroyImmediate(transport.gameObject);
}
}
@@ -42,6 +42,7 @@ namespace Unity.Netcode.RuntimeTests
transportEvents?.Clear();
}
UnityTransportTestComponent.CleanUp();
yield return null;
}
@@ -59,6 +60,7 @@ namespace Unity.Netcode.RuntimeTests
Assert.False(m_Clients[0].StartClient(), "Client failed to detect invalid endpoint!");
netcodeLogAssert.LogWasReceived(LogType.Error, $"Network listen address ({m_Server.ConnectionData.Address}) is Invalid!");
netcodeLogAssert.LogWasReceived(LogType.Error, $"Target server network address ({m_Clients[0].ConnectionData.Address}) is Invalid!");
UnityTransportTestComponent.CleanUp();
}
// Check connection with a single client.
@@ -161,8 +163,6 @@ namespace Unity.Netcode.RuntimeTests
// Check that all clients got a Disconnect event.
Assert.True(m_ClientsEvents.All(evs => evs.Count == 2));
Assert.True(m_ClientsEvents.All(evs => evs[1].Type == NetworkEvent.Disconnect));
yield return null;
}
// Check client disconnection from a single client.
@@ -188,13 +188,11 @@ namespace Unity.Netcode.RuntimeTests
{
InitializeTransport(out m_Server, out m_ServerEvents);
m_Server.StartServer();
for (int i = 0; i < k_NumClients; i++)
{
InitializeTransport(out m_Clients[i], out m_ClientsEvents[i]);
m_Clients[i].StartClient();
}
yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ClientsEvents[k_NumClients - 1]);
// Disconnect a single client.
@@ -207,14 +205,11 @@ namespace Unity.Netcode.RuntimeTests
{
m_Clients[i].DisconnectLocalClient();
}
yield return WaitForNetworkEvent(NetworkEvent.Disconnect, m_ServerEvents, 5);
// Check that we got the correct number of Disconnect events on the server.
Assert.AreEqual(k_NumClients * 2, m_ServerEvents.Count);
Assert.AreEqual(k_NumClients, m_ServerEvents.Count(e => e.Type == NetworkEvent.Disconnect));
yield return null;
}
// Check that server re-disconnects are no-ops.
@@ -244,8 +239,6 @@ namespace Unity.Netcode.RuntimeTests
// Check we haven't received anything else on the client or server.
Assert.AreEqual(m_ServerEvents.Count, previousServerEventsCount);
Assert.AreEqual(m_ClientsEvents[0].Count, previousClientEventsCount);
yield return null;
}
// Check that client re-disconnects are no-ops.
@@ -275,8 +268,6 @@ namespace Unity.Netcode.RuntimeTests
// Check we haven't received anything else on the client or server.
Assert.AreEqual(m_ServerEvents.Count, previousServerEventsCount);
Assert.AreEqual(m_ClientsEvents[0].Count, previousClientEventsCount);
yield return null;
}
// Check connection with different server/listen addresses.

View File

@@ -36,6 +36,24 @@ namespace Unity.Netcode.RuntimeTests
Assert.Fail("Timed out while waiting for network event.");
}
// Wait to ensure no event is sent.
public static IEnumerator EnsureNoNetworkEvent(List<TransportEvent> events, float timeout = MaxNetworkEventWaitTime)
{
int initialCount = events.Count;
float startTime = Time.realtimeSinceStartup;
while (Time.realtimeSinceStartup - startTime < timeout)
{
if (events.Count > initialCount)
{
Assert.Fail("Received unexpected network event.");
}
yield return new WaitForSeconds(0.01f);
}
}
// Common code to initialize a UnityTransport that logs its events.
public static void InitializeTransport(out UnityTransport transport, out List<TransportEvent> events,
int maxPayloadSize = UnityTransport.InitialMaxPayloadSize, int maxSendQueueSize = 0, NetworkFamily family = NetworkFamily.Ipv4)
@@ -43,7 +61,7 @@ namespace Unity.Netcode.RuntimeTests
var logger = new TransportEventLogger();
events = logger.Events;
transport = new GameObject().AddComponent<UnityTransport>();
transport = new GameObject().AddComponent<UnityTransportTestComponent>();
transport.OnTransportEvent += logger.HandleEvent;
transport.MaxPayloadSize = maxPayloadSize;
@@ -91,5 +109,58 @@ namespace Unity.Netcode.RuntimeTests
});
}
}
internal class UnityTransportTestComponent : UnityTransport, INetworkUpdateSystem
{
private static List<UnityTransportTestComponent> s_Instances = new List<UnityTransportTestComponent>();
public static void CleanUp()
{
for (int i = s_Instances.Count - 1; i >= 0; i--)
{
var instance = s_Instances[i];
instance.Shutdown();
DestroyImmediate(instance.gameObject);
}
s_Instances.Clear();
}
/// <summary>
/// Simulate the <see cref="NetworkManager.NetworkUpdate"/> being invoked so <see cref="UnityTransport.EarlyUpdate"/>
/// and <see cref="UnityTransport.PostLateUpdate"/> are invoked.
/// </summary>
public void NetworkUpdate(NetworkUpdateStage updateStage)
{
switch (updateStage)
{
case NetworkUpdateStage.EarlyUpdate:
{
EarlyUpdate();
break;
}
case NetworkUpdateStage.PostLateUpdate:
{
PostLateUpdate();
break;
}
}
}
public override void Shutdown()
{
s_Instances.Remove(this);
base.Shutdown();
this.UnregisterAllNetworkUpdates();
}
public override void Initialize(NetworkManager networkManager = null)
{
base.Initialize(networkManager);
this.RegisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate);
this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate);
this.RegisterNetworkUpdate(NetworkUpdateStage.PostLateUpdate);
s_Instances.Add(this);
}
}
}
}

View File

@@ -60,11 +60,11 @@ namespace Unity.Netcode.RuntimeTests
// Need to destroy the GameObject (all assigned components will get destroyed too)
UnityEngine.Object.DestroyImmediate(m_Client2.gameObject);
}
m_ServerEvents?.Clear();
m_Client1Events?.Clear();
m_Client2Events?.Clear();
UnityTransportTestComponent.CleanUp();
yield return null;
}
@@ -217,8 +217,6 @@ namespace Unity.Netcode.RuntimeTests
Assert.AreEqual(33, m_ServerEvents[3].Data.First());
Assert.AreEqual(10, m_ServerEvents[3].Data.Count);
yield return null;
}
// Check sending data to multiple clients.
@@ -260,8 +258,6 @@ namespace Unity.Netcode.RuntimeTests
byte c1Data = m_Client1Events[1].Data.First();
byte c2Data = m_Client2Events[1].Data.First();
Assert.That((c1Data == 11 && c2Data == 22) || (c1Data == 22 && c2Data == 11));
yield return null;
}
// Check receiving data from multiple clients.
@@ -299,8 +295,6 @@ namespace Unity.Netcode.RuntimeTests
byte sData1 = m_ServerEvents[2].Data.First();
byte sData2 = m_ServerEvents[3].Data.First();
Assert.That((sData1 == 11 && sData2 == 22) || (sData1 == 22 && sData2 == 11));
yield return null;
}
// Check that we get disconnected when overflowing the reliable send queue.
@@ -332,8 +326,6 @@ namespace Unity.Netcode.RuntimeTests
Assert.AreEqual(2, m_Client1Events.Count);
Assert.AreEqual(NetworkEvent.Disconnect, m_Client1Events[1].Type);
yield return null;
}
// Check that it's fine to overflow the unreliable send queue (traffic is flushed on overflow).
@@ -373,8 +365,6 @@ namespace Unity.Netcode.RuntimeTests
Assert.AreEqual(NetworkEvent.Data, m_ServerEvents[i].Type);
Assert.AreEqual(1024, m_ServerEvents[i].Data.Count);
}
yield return null;
}
#if !UTP_TRANSPORT_2_0_ABOVE
@@ -451,8 +441,6 @@ namespace Unity.Netcode.RuntimeTests
m_Client1.Shutdown();
yield return WaitForNetworkEvent(NetworkEvent.Data, m_ServerEvents);
yield return null;
}
[UnityTest]
@@ -472,8 +460,6 @@ namespace Unity.Netcode.RuntimeTests
m_Client1.DisconnectLocalClient();
yield return WaitForNetworkEvent(NetworkEvent.Data, m_ServerEvents);
yield return null;
}
[UnityTest]
@@ -493,8 +479,6 @@ namespace Unity.Netcode.RuntimeTests
m_Server.DisconnectRemoteClient(m_ServerEvents[0].ClientID);
yield return WaitForNetworkEvent(NetworkEvent.Data, m_Client1Events);
yield return null;
}
[UnityTest]
@@ -514,8 +498,47 @@ namespace Unity.Netcode.RuntimeTests
m_Server.Send(m_Client1.ServerClientId, data, NetworkDelivery.Reliable);
yield return WaitForNetworkEvent(NetworkEvent.Data, m_Client1Events);
}
yield return null;
public enum AfterShutdownAction
{
Send,
DisconnectRemoteClient,
DisconnectLocalClient,
}
[UnityTest]
public IEnumerator DoesNotActAfterShutdown([Values] AfterShutdownAction afterShutdownAction)
{
InitializeTransport(out m_Server, out m_ServerEvents);
InitializeTransport(out m_Client1, out m_Client1Events);
m_Server.StartServer();
m_Client1.StartClient();
yield return WaitForNetworkEvent(NetworkEvent.Connect, m_Client1Events);
m_Server.Shutdown();
if (afterShutdownAction == AfterShutdownAction.Send)
{
var data = new ArraySegment<byte>(new byte[16]);
m_Server.Send(m_Client1.ServerClientId, data, NetworkDelivery.Reliable);
yield return EnsureNoNetworkEvent(m_Client1Events);
}
else if (afterShutdownAction == AfterShutdownAction.DisconnectRemoteClient)
{
m_Server.DisconnectRemoteClient(m_Client1.ServerClientId);
LogAssert.Expect(LogType.Assert, "DisconnectRemoteClient should be called on a listening server");
}
else if (afterShutdownAction == AfterShutdownAction.DisconnectLocalClient)
{
m_Server.DisconnectLocalClient();
yield return EnsureNoNetworkEvent(m_Client1Events);
}
}
}
}