This repository has been archived on 2025-04-22. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs
Unity Technologies add668dfd2 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)
2022-04-27 00:00:00 +00:00

267 lines
14 KiB
C#

using System;
using System.Collections;
#if NGO_TRANSFORM_DEBUG
using System.Text.RegularExpressions;
#endif
using Unity.Netcode.Components;
using NUnit.Framework;
// using Unity.Netcode.Samples;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime;
namespace Unity.Netcode.RuntimeTests
{
public class NetworkTransformTestComponent : NetworkTransform
{
public bool ReadyToReceivePositionUpdate = false;
public override void OnNetworkSpawn()
{
base.OnNetworkSpawn();
ReadyToReceivePositionUpdate = true;
}
public (bool isDirty, bool isPositionDirty, bool isRotationDirty, bool isScaleDirty) ApplyState()
{
return ApplyLocalNetworkState(transform);
}
}
// [TestFixture(true, true)]
[TestFixture(true, false)]
// [TestFixture(false, true)]
[TestFixture(false, false)]
public class NetworkTransformTests : NetcodeIntegrationTest
{
private NetworkObject m_ClientSideClientPlayer;
private NetworkObject m_ServerSideClientPlayer;
private readonly bool m_TestWithClientNetworkTransform;
public NetworkTransformTests(bool testWithHost, bool testWithClientNetworkTransform)
{
m_UseHost = testWithHost; // from test fixture
m_TestWithClientNetworkTransform = testWithClientNetworkTransform;
}
protected override int NumberOfClients => 1;
protected override void OnCreatePlayerPrefab()
{
if (m_TestWithClientNetworkTransform)
{
// m_PlayerPrefab.AddComponent<ClientNetworkTransform>();
}
else
{
var networkTransform = m_PlayerPrefab.AddComponent<NetworkTransformTestComponent>();
networkTransform.Interpolate = false;
}
}
protected override void OnServerAndClientsCreated()
{
#if NGO_TRANSFORM_DEBUG
// Log assert for writing without authority is a developer log...
// TODO: This is why monolithic test base classes and test helpers are an anti-pattern - this is part of an individual test case setup but is separated from the code verifying it!
m_ServerNetworkManager.LogLevel = LogLevel.Developer;
m_ClientNetworkManagers[0].LogLevel = LogLevel.Developer;
#endif
}
protected override IEnumerator OnServerAndClientsConnected()
{
// Get the client player representation on both the server and the client side
m_ServerSideClientPlayer = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId];
m_ClientSideClientPlayer = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ClientNetworkManagers[0].LocalClientId];
// Get the NetworkTransformTestComponent to make sure the client side is ready before starting test
var otherSideNetworkTransformComponent = m_ClientSideClientPlayer.GetComponent<NetworkTransformTestComponent>();
// Wait for the client-side to notify it is finished initializing and spawning.
yield return WaitForConditionOrTimeOut(() => otherSideNetworkTransformComponent.ReadyToReceivePositionUpdate == true);
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for client-side to notify it is ready!");
yield return base.OnServerAndClientsConnected();
}
// TODO: rewrite after perms & authority changes
[UnityTest]
public IEnumerator TestAuthoritativeTransformChangeOneAtATime([Values] bool testLocalTransform)
{
// Get the client player's NetworkTransform for both instances
var authoritativeNetworkTransform = m_ServerSideClientPlayer.GetComponent<NetworkTransform>();
var otherSideNetworkTransform = m_ClientSideClientPlayer.GetComponent<NetworkTransform>();
Assert.That(!otherSideNetworkTransform.CanCommitToTransform);
Assert.That(authoritativeNetworkTransform.CanCommitToTransform);
if (authoritativeNetworkTransform.CanCommitToTransform)
{
authoritativeNetworkTransform.InLocalSpace = testLocalTransform;
}
if (otherSideNetworkTransform.CanCommitToTransform)
{
otherSideNetworkTransform.InLocalSpace = testLocalTransform;
}
float approximation = 0.05f;
// test position
var authPlayerTransform = authoritativeNetworkTransform.transform;
Assert.AreEqual(Vector3.zero, otherSideNetworkTransform.transform.position, "server side pos should be zero at first"); // sanity check
authPlayerTransform.position = new Vector3(10, 20, 30);
yield return WaitForConditionOrTimeOut(() => otherSideNetworkTransform.transform.position.x > approximation);
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"timeout while waiting for position change! Otherside value {otherSideNetworkTransform.transform.position.x} vs. Approximation {approximation}");
Assert.True(new Vector3(10, 20, 30) == otherSideNetworkTransform.transform.position, $"wrong position on ghost, {otherSideNetworkTransform.transform.position}"); // Vector3 already does float approximation with ==
// test rotation
authPlayerTransform.rotation = Quaternion.Euler(45, 40, 35); // using euler angles instead of quaternions directly to really see issues users might encounter
Assert.AreEqual(Quaternion.identity, otherSideNetworkTransform.transform.rotation, "wrong initial value for rotation"); // sanity check
yield return WaitForConditionOrTimeOut(() => otherSideNetworkTransform.transform.rotation.eulerAngles.x > approximation);
Assert.False(s_GlobalTimeoutHelper.TimedOut, "timeout while waiting for rotation change");
// approximation needed here since eulerAngles isn't super precise.
Assert.LessOrEqual(Math.Abs(45 - otherSideNetworkTransform.transform.rotation.eulerAngles.x), approximation, $"wrong rotation on ghost on x, got {otherSideNetworkTransform.transform.rotation.eulerAngles.x}");
Assert.LessOrEqual(Math.Abs(40 - otherSideNetworkTransform.transform.rotation.eulerAngles.y), approximation, $"wrong rotation on ghost on y, got {otherSideNetworkTransform.transform.rotation.eulerAngles.y}");
Assert.LessOrEqual(Math.Abs(35 - otherSideNetworkTransform.transform.rotation.eulerAngles.z), approximation, $"wrong rotation on ghost on z, got {otherSideNetworkTransform.transform.rotation.eulerAngles.z}");
// test scale
UnityEngine.Assertions.Assert.AreApproximatelyEqual(1f, otherSideNetworkTransform.transform.lossyScale.x, "wrong initial value for scale"); // sanity check
UnityEngine.Assertions.Assert.AreApproximatelyEqual(1f, otherSideNetworkTransform.transform.lossyScale.y, "wrong initial value for scale"); // sanity check
UnityEngine.Assertions.Assert.AreApproximatelyEqual(1f, otherSideNetworkTransform.transform.lossyScale.z, "wrong initial value for scale"); // sanity check
authPlayerTransform.localScale = new Vector3(2, 3, 4);
yield return WaitForConditionOrTimeOut(() => otherSideNetworkTransform.transform.lossyScale.x > 1f + approximation);
Assert.False(s_GlobalTimeoutHelper.TimedOut, "timeout while waiting for scale change");
UnityEngine.Assertions.Assert.AreApproximatelyEqual(2f, otherSideNetworkTransform.transform.lossyScale.x, "wrong scale on ghost");
UnityEngine.Assertions.Assert.AreApproximatelyEqual(3f, otherSideNetworkTransform.transform.lossyScale.y, "wrong scale on ghost");
UnityEngine.Assertions.Assert.AreApproximatelyEqual(4f, otherSideNetworkTransform.transform.lossyScale.z, "wrong scale on ghost");
// todo reparent and test
// todo test all public API
}
[UnityTest]
public IEnumerator TestCantChangeTransformFromOtherSideAuthority([Values] bool testClientAuthority)
{
// Get the client player's NetworkTransform for both instances
var authoritativeNetworkTransform = m_ServerSideClientPlayer.GetComponent<NetworkTransform>();
var otherSideNetworkTransform = m_ClientSideClientPlayer.GetComponent<NetworkTransform>();
Assert.AreEqual(Vector3.zero, otherSideNetworkTransform.transform.position, "other side pos should be zero at first"); // sanity check
otherSideNetworkTransform.transform.position = new Vector3(4, 5, 6);
yield return s_DefaultWaitForTick;
Assert.AreEqual(Vector3.zero, otherSideNetworkTransform.transform.position, "got authority error, but other side still moved!");
#if NGO_TRANSFORM_DEBUG
// We are no longer emitting this warning, and we are banishing tests that rely on console output, so
// needs re-implementation
// TODO: This should be a separate test - verify 1 behavior per test
LogAssert.Expect(LogType.Warning, new Regex(".*without authority detected.*"));
#endif
}
/// <summary>
/// Validates that rotation checks don't produce false positive
/// results when rolling over between 0 and 360 degrees
/// </summary>
[UnityTest]
public IEnumerator TestRotationThresholdDeltaCheck()
{
// Get the client player's NetworkTransform for both instances
var authoritativeNetworkTransform = m_ServerSideClientPlayer.GetComponent<NetworkTransformTestComponent>();
var otherSideNetworkTransform = m_ClientSideClientPlayer.GetComponent<NetworkTransformTestComponent>();
otherSideNetworkTransform.RotAngleThreshold = authoritativeNetworkTransform.RotAngleThreshold = 5.0f;
var halfThreshold = authoritativeNetworkTransform.RotAngleThreshold * 0.5001f;
var serverRotation = authoritativeNetworkTransform.transform.rotation;
var serverEulerRotation = serverRotation.eulerAngles;
// Verify rotation is not marked dirty when rotated by half of the threshold
serverEulerRotation.y += halfThreshold;
serverRotation.eulerAngles = serverEulerRotation;
authoritativeNetworkTransform.transform.rotation = serverRotation;
var results = authoritativeNetworkTransform.ApplyState();
Assert.IsFalse(results.isRotationDirty, $"Rotation is dirty when rotation threshold is {authoritativeNetworkTransform.RotAngleThreshold} degrees and only adjusted by {halfThreshold} degrees!");
yield return s_DefaultWaitForTick;
// Verify rotation is marked dirty when rotated by another half threshold value
serverEulerRotation.y += halfThreshold;
serverRotation.eulerAngles = serverEulerRotation;
authoritativeNetworkTransform.transform.rotation = serverRotation;
results = authoritativeNetworkTransform.ApplyState();
Assert.IsTrue(results.isRotationDirty, $"Rotation was not dirty when rotated by the threshold value: {authoritativeNetworkTransform.RotAngleThreshold} degrees!");
yield return s_DefaultWaitForTick;
//Reset rotation back to zero on all axis
serverRotation.eulerAngles = serverEulerRotation = Vector3.zero;
authoritativeNetworkTransform.transform.rotation = serverRotation;
yield return s_DefaultWaitForTick;
// Rotate by 360 minus halfThreshold (which is really just negative halfThreshold) and verify rotation is not marked dirty
serverEulerRotation.y = 360 - halfThreshold;
serverRotation.eulerAngles = serverEulerRotation;
authoritativeNetworkTransform.transform.rotation = serverRotation;
results = authoritativeNetworkTransform.ApplyState();
Assert.IsFalse(results.isRotationDirty, $"Rotation is dirty when rotation threshold is {authoritativeNetworkTransform.RotAngleThreshold} degrees and only adjusted by " +
$"{Mathf.DeltaAngle(0, serverEulerRotation.y)} degrees!");
serverEulerRotation.y -= halfThreshold;
serverRotation.eulerAngles = serverEulerRotation;
authoritativeNetworkTransform.transform.rotation = serverRotation;
results = authoritativeNetworkTransform.ApplyState();
Assert.IsTrue(results.isRotationDirty, $"Rotation was not dirty when rotated by {Mathf.DeltaAngle(0, serverEulerRotation.y)} degrees!");
//Reset rotation back to zero on all axis
serverRotation.eulerAngles = serverEulerRotation = Vector3.zero;
authoritativeNetworkTransform.transform.rotation = serverRotation;
yield return s_DefaultWaitForTick;
serverEulerRotation.y -= halfThreshold;
serverRotation.eulerAngles = serverEulerRotation;
authoritativeNetworkTransform.transform.rotation = serverRotation;
results = authoritativeNetworkTransform.ApplyState();
Assert.IsFalse(results.isRotationDirty, $"Rotation is dirty when rotation threshold is {authoritativeNetworkTransform.RotAngleThreshold} degrees and only adjusted by " +
$"{Mathf.DeltaAngle(0, serverEulerRotation.y)} degrees!");
serverEulerRotation.y -= halfThreshold;
serverRotation.eulerAngles = serverEulerRotation;
authoritativeNetworkTransform.transform.rotation = serverRotation;
results = authoritativeNetworkTransform.ApplyState();
Assert.IsTrue(results.isRotationDirty, $"Rotation was not dirty when rotated by {Mathf.DeltaAngle(0, serverEulerRotation.y)} degrees!");
}
/*
* ownership change
* test teleport with interpolation
* test teleport without interpolation
* test dynamic spawning
*/
protected override IEnumerator OnTearDown()
{
UnityEngine.Object.DestroyImmediate(m_PlayerPrefab);
yield return base.OnTearDown();
}
}
}