com.unity.netcode.gameobjects@1.0.0-pre.8
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.0.0-pre.8] - 2022-04-27 ### Changed - `unmanaged` structs are no longer universally accepted as RPC parameters because some structs (i.e., structs with pointers in them, such as `NativeList<T>`) can't be supported by the default memcpy struct serializer. Structs that are intended to be serialized across the network must add `INetworkSerializeByMemcpy` to the interface list (i.e., `struct Foo : INetworkSerializeByMemcpy`). This interface is empty and just serves to mark the struct as compatible with memcpy serialization. For external structs you can't edit, you can pass them to RPCs by wrapping them in `ForceNetworkSerializeByMemcpy<T>`. (#1901) ### Removed - Removed `SIPTransport` (#1870) - Removed `ClientNetworkTransform` from the package samples and moved to Boss Room's Utilities package which can be found [here](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/main/Packages/com.unity.multiplayer.samples.coop/Utilities/Net/ClientAuthority/ClientNetworkTransform.cs). ### Fixed - Fixed `NetworkTransform` generating false positive rotation delta checks when rolling over between 0 and 360 degrees. (#1890) - Fixed client throwing an exception if it has messages in the outbound queue when processing the `NetworkEvent.Disconnect` event and is using UTP. (#1884) - Fixed issue during client synchronization if 'ValidateSceneBeforeLoading' returned false it would halt the client synchronization process resulting in a client that was approved but not synchronized or fully connected with the server. (#1883) - Fixed an issue where UNetTransport.StartServer would return success even if the underlying transport failed to start (#854) - Passing generic types to RPCs no longer causes a native crash (#1901) - Fixed an issue where calling `Shutdown` on a `NetworkManager` that was already shut down would cause an immediate shutdown the next time it was started (basically the fix makes `Shutdown` idempotent). (#1877)
This commit is contained in:
277
Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs
Normal file
277
Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs
Normal file
@@ -0,0 +1,277 @@
|
||||
#if COM_UNITY_MODULES_PHYSICS
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unity.Netcode.Components;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
using Unity.Netcode.TestHelpers.Runtime;
|
||||
namespace Unity.Netcode.RuntimeTests
|
||||
{
|
||||
public class NetworkTransformOwnershipTests : NetcodeIntegrationTest
|
||||
{
|
||||
protected override int NumberOfClients => 1;
|
||||
|
||||
private GameObject m_ClientNetworkTransformPrefab;
|
||||
private GameObject m_NetworkTransformPrefab;
|
||||
|
||||
protected override void OnServerAndClientsCreated()
|
||||
{
|
||||
VerifyObjectIsSpawnedOnClient.ResetObjectTable();
|
||||
m_ClientNetworkTransformPrefab = CreateNetworkObjectPrefab("OwnerAuthorityTest");
|
||||
var clientNetworkTransform = m_ClientNetworkTransformPrefab.AddComponent<TestClientNetworkTransform>();
|
||||
clientNetworkTransform.Interpolate = false;
|
||||
var rigidBody = m_ClientNetworkTransformPrefab.AddComponent<Rigidbody>();
|
||||
rigidBody.useGravity = false;
|
||||
m_ClientNetworkTransformPrefab.AddComponent<NetworkRigidbody>();
|
||||
m_ClientNetworkTransformPrefab.AddComponent<SphereCollider>();
|
||||
m_ClientNetworkTransformPrefab.AddComponent<VerifyObjectIsSpawnedOnClient>();
|
||||
|
||||
m_NetworkTransformPrefab = CreateNetworkObjectPrefab("ServerAuthorityTest");
|
||||
var networkTransform = m_NetworkTransformPrefab.AddComponent<NetworkTransform>();
|
||||
rigidBody = m_NetworkTransformPrefab.AddComponent<Rigidbody>();
|
||||
rigidBody.useGravity = false;
|
||||
m_NetworkTransformPrefab.AddComponent<NetworkRigidbody>();
|
||||
m_NetworkTransformPrefab.AddComponent<SphereCollider>();
|
||||
m_NetworkTransformPrefab.AddComponent<VerifyObjectIsSpawnedOnClient>();
|
||||
networkTransform.Interpolate = false;
|
||||
|
||||
base.OnServerAndClientsCreated();
|
||||
}
|
||||
|
||||
public enum StartingOwnership
|
||||
{
|
||||
HostStartsAsOwner,
|
||||
ClientStartsAsOwner,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This verifies that when authority is owner authoritative the owner's
|
||||
/// Rigidbody is kinematic and the non-owner's is not.
|
||||
/// This also verifies that we can switch between owners and that only the
|
||||
/// owner can update the transform while non-owners cannot.
|
||||
/// </summary>
|
||||
/// <param name="spawnWithHostOwnership">determines who starts as the owner (true): host | (false): client</param>
|
||||
[UnityTest]
|
||||
public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwnership)
|
||||
{
|
||||
// Get the current ownership layout
|
||||
var networkManagerOwner = startingOwnership == StartingOwnership.HostStartsAsOwner ? m_ServerNetworkManager : m_ClientNetworkManagers[0];
|
||||
var networkManagerNonOwner = startingOwnership == StartingOwnership.HostStartsAsOwner ? m_ClientNetworkManagers[0] : m_ServerNetworkManager;
|
||||
|
||||
// Spawn the m_ClientNetworkTransformPrefab and wait for the client-side to spawn the object
|
||||
var serverSideInstance = SpawnObject(m_ClientNetworkTransformPrefab, networkManagerOwner);
|
||||
yield return WaitForConditionOrTimeOut(() => VerifyObjectIsSpawnedOnClient.GetClientsThatSpawnedThisPrefab().Contains(m_ClientNetworkManagers[0].LocalClientId));
|
||||
|
||||
// Get owner relative instances
|
||||
var ownerInstance = VerifyObjectIsSpawnedOnClient.GetClientInstance(networkManagerOwner.LocalClientId);
|
||||
var nonOwnerInstance = VerifyObjectIsSpawnedOnClient.GetClientInstance(networkManagerNonOwner.LocalClientId);
|
||||
Assert.NotNull(ownerInstance);
|
||||
Assert.NotNull(nonOwnerInstance);
|
||||
|
||||
// Make sure the owner is not kinematic and the non-owner(s) are kinematic
|
||||
Assert.True(nonOwnerInstance.GetComponent<Rigidbody>().isKinematic, $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} is not kinematic when it should be!");
|
||||
Assert.False(ownerInstance.GetComponent<Rigidbody>().isKinematic, $"{networkManagerOwner.name}'s object instance {ownerInstance.name} is kinematic when it should not be!");
|
||||
|
||||
// Owner changes transform values
|
||||
var valueSetByOwner = Vector3.one * 2;
|
||||
ownerInstance.transform.position = valueSetByOwner;
|
||||
ownerInstance.transform.localScale = valueSetByOwner;
|
||||
var rotation = new Quaternion();
|
||||
rotation.eulerAngles = valueSetByOwner;
|
||||
ownerInstance.transform.rotation = rotation;
|
||||
var transformToTest = nonOwnerInstance.transform;
|
||||
yield return WaitForConditionOrTimeOut(() => transformToTest.position == valueSetByOwner && transformToTest.localScale == valueSetByOwner && transformToTest.rotation == rotation);
|
||||
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}");
|
||||
|
||||
// Verify non-owners cannot change transform values
|
||||
nonOwnerInstance.transform.position = Vector3.zero;
|
||||
yield return s_DefaultWaitForTick;
|
||||
Assert.True(nonOwnerInstance.transform.position == valueSetByOwner, $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {Vector3.one} Is Currently:{nonOwnerInstance.transform.position}");
|
||||
|
||||
// Change ownership and wait for the non-owner to reflect the change
|
||||
VerifyObjectIsSpawnedOnClient.ResetObjectTable();
|
||||
m_ServerNetworkManager.SpawnManager.ChangeOwnership(serverSideInstance.GetComponent<NetworkObject>(), networkManagerNonOwner.LocalClientId);
|
||||
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!");
|
||||
|
||||
// Re-assign the ownership references and wait for the non-owner instance to be notified of ownership change
|
||||
networkManagerOwner = startingOwnership == StartingOwnership.HostStartsAsOwner ? m_ClientNetworkManagers[0] : m_ServerNetworkManager;
|
||||
networkManagerNonOwner = startingOwnership == StartingOwnership.HostStartsAsOwner ? m_ServerNetworkManager : m_ClientNetworkManagers[0];
|
||||
ownerInstance = VerifyObjectIsSpawnedOnClient.GetClientInstance(networkManagerOwner.LocalClientId);
|
||||
Assert.NotNull(ownerInstance);
|
||||
yield return WaitForConditionOrTimeOut(() => VerifyObjectIsSpawnedOnClient.GetClientInstance(networkManagerNonOwner.LocalClientId) != null);
|
||||
nonOwnerInstance = VerifyObjectIsSpawnedOnClient.GetClientInstance(networkManagerNonOwner.LocalClientId);
|
||||
Assert.NotNull(nonOwnerInstance);
|
||||
|
||||
// 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!");
|
||||
Assert.True(nonOwnerInstance.GetComponent<Rigidbody>().isKinematic, $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} is not kinematic when it should be!");
|
||||
|
||||
// Have the new owner change transform values and wait for those values to be applied on the non-owner side.
|
||||
valueSetByOwner = Vector3.one * 50;
|
||||
ownerInstance.transform.position = valueSetByOwner;
|
||||
ownerInstance.transform.localScale = valueSetByOwner;
|
||||
rotation.eulerAngles = valueSetByOwner;
|
||||
ownerInstance.transform.rotation = rotation;
|
||||
transformToTest = nonOwnerInstance.transform;
|
||||
yield return WaitForConditionOrTimeOut(() => transformToTest.position == valueSetByOwner && transformToTest.localScale == valueSetByOwner && transformToTest.rotation == rotation);
|
||||
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}");
|
||||
|
||||
// The last check is to verify non-owners cannot change transform values after ownership has changed
|
||||
nonOwnerInstance.transform.position = Vector3.zero;
|
||||
yield return s_DefaultWaitForTick;
|
||||
Assert.True(nonOwnerInstance.transform.position == valueSetByOwner, $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {Vector3.one} Is Currently:{nonOwnerInstance.transform.position}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This verifies that when authority is server authoritative the
|
||||
/// client's Rigidbody is kinematic and the server is not.
|
||||
/// This also verifies only the server can apply updates to the
|
||||
/// transform while the clients cannot.
|
||||
/// </summary>
|
||||
[UnityTest]
|
||||
public IEnumerator ServerAuthoritativeTest()
|
||||
{
|
||||
// Spawn the m_NetworkTransformPrefab and wait for the client-side to spawn the object
|
||||
var serverSideInstance = SpawnObject(m_NetworkTransformPrefab, m_ServerNetworkManager);
|
||||
yield return WaitForConditionOrTimeOut(() => VerifyObjectIsSpawnedOnClient.GetClientsThatSpawnedThisPrefab().Contains(m_ClientNetworkManagers[0].LocalClientId));
|
||||
|
||||
var ownerInstance = VerifyObjectIsSpawnedOnClient.GetClientInstance(m_ServerNetworkManager.LocalClientId);
|
||||
var nonOwnerInstance = VerifyObjectIsSpawnedOnClient.GetClientInstance(m_ClientNetworkManagers[0].LocalClientId);
|
||||
|
||||
// Make sure the owner is not kinematic and the non-owner(s) are kinematic
|
||||
Assert.False(ownerInstance.GetComponent<Rigidbody>().isKinematic, $"{m_ServerNetworkManager.name}'s object instance {ownerInstance.name} is kinematic when it should not be!");
|
||||
Assert.True(nonOwnerInstance.GetComponent<Rigidbody>().isKinematic, $"{m_ClientNetworkManagers[0].name}'s object instance {nonOwnerInstance.name} is not kinematic when it should be!");
|
||||
|
||||
// Server changes transform values
|
||||
var valueSetByOwner = Vector3.one * 2;
|
||||
ownerInstance.transform.position = valueSetByOwner;
|
||||
ownerInstance.transform.localScale = valueSetByOwner;
|
||||
var rotation = new Quaternion();
|
||||
rotation.eulerAngles = valueSetByOwner;
|
||||
ownerInstance.transform.rotation = rotation;
|
||||
var transformToTest = nonOwnerInstance.transform;
|
||||
yield return WaitForConditionOrTimeOut(() => transformToTest.position == valueSetByOwner && transformToTest.localScale == valueSetByOwner && transformToTest.rotation == rotation);
|
||||
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {m_ClientNetworkManagers[0].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}");
|
||||
|
||||
// The last check is to verify clients cannot change transform values
|
||||
nonOwnerInstance.transform.position = Vector3.zero;
|
||||
yield return s_DefaultWaitForTick;
|
||||
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}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// NetworkTransformOwnershipTests helper behaviour
|
||||
/// </summary>
|
||||
public class VerifyObjectIsSpawnedOnClient : NetworkBehaviour
|
||||
{
|
||||
private static Dictionary<ulong, VerifyObjectIsSpawnedOnClient> s_NetworkManagerRelativeSpawnedObjects = new Dictionary<ulong, VerifyObjectIsSpawnedOnClient>();
|
||||
|
||||
public static void ResetObjectTable()
|
||||
{
|
||||
s_NetworkManagerRelativeSpawnedObjects.Clear();
|
||||
}
|
||||
|
||||
public override void OnGainedOwnership()
|
||||
{
|
||||
if (!s_NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId))
|
||||
{
|
||||
s_NetworkManagerRelativeSpawnedObjects.Add(NetworkManager.LocalClientId, this);
|
||||
}
|
||||
base.OnGainedOwnership();
|
||||
}
|
||||
|
||||
public override void OnLostOwnership()
|
||||
{
|
||||
if (!s_NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId))
|
||||
{
|
||||
s_NetworkManagerRelativeSpawnedObjects.Add(NetworkManager.LocalClientId, this);
|
||||
}
|
||||
base.OnLostOwnership();
|
||||
}
|
||||
|
||||
public static List<ulong> GetClientsThatSpawnedThisPrefab()
|
||||
{
|
||||
return s_NetworkManagerRelativeSpawnedObjects.Keys.ToList();
|
||||
}
|
||||
|
||||
public static VerifyObjectIsSpawnedOnClient GetClientInstance(ulong clientId)
|
||||
{
|
||||
if (s_NetworkManagerRelativeSpawnedObjects.ContainsKey(clientId))
|
||||
{
|
||||
return s_NetworkManagerRelativeSpawnedObjects[clientId];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
// This makes sure that the NetworkManager relative NetworkObject instances don't collide with each other
|
||||
// and skew the expected changes to the transforms
|
||||
foreach (var entry in s_NetworkManagerRelativeSpawnedObjects)
|
||||
{
|
||||
Physics.IgnoreCollision(entry.Value.GetComponent<SphereCollider>(), GetComponent<SphereCollider>());
|
||||
}
|
||||
|
||||
if (!s_NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId))
|
||||
{
|
||||
s_NetworkManagerRelativeSpawnedObjects.Add(NetworkManager.LocalClientId, this);
|
||||
}
|
||||
base.OnNetworkSpawn();
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
if (s_NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId))
|
||||
{
|
||||
s_NetworkManagerRelativeSpawnedObjects.Remove(NetworkManager.LocalClientId);
|
||||
}
|
||||
base.OnNetworkDespawn();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Until we can better locate the ClientNetworkTransform
|
||||
/// This will have to be used to verify the ownership authority
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public 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 OnIsServerAuthoritatitive()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user