Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4427d35
base
SamuelBellomo May 21, 2021
5eee587
base tests work, more tests incoming
SamuelBellomo May 21, 2021
923b49c
authority tests work
SamuelBellomo May 21, 2021
3a43905
adding server vs client authority tests
SamuelBellomo May 21, 2021
2a496a6
Merge branch 'develop' into feature/adding-tests-for-network-transform
SamuelBellomo May 21, 2021
794ca2f
wip
SamuelBellomo May 21, 2021
293a28a
Merge branch 'develop' into feature/adding-tests-for-network-transform
SamuelBellomo May 26, 2021
914bb35
Fixing issue where NetworkBehaviour had static attributes, making net…
SamuelBellomo May 28, 2021
506e1a2
#
SamuelBellomo May 31, 2021
3436084
revert useless changes
SamuelBellomo May 31, 2021
d4b6a24
#
SamuelBellomo May 31, 2021
09eaf82
Merge branch 'develop' into feature/adding-tests-for-network-transform
SamuelBellomo May 31, 2021
31e95a0
formatting
SamuelBellomo May 31, 2021
16069f0
adding doc
SamuelBellomo May 31, 2021
30d38a9
better comments
SamuelBellomo Jun 21, 2021
7a73d66
Merge branch 'develop' into feature/adding-tests-for-network-transform
SamuelBellomo Jun 21, 2021
f0a7755
cleanup
SamuelBellomo Jun 21, 2021
d134e89
reverting change to move to a separate PR
SamuelBellomo Jun 21, 2021
b3d89cd
moving this to a separate PR
SamuelBellomo Jun 21, 2021
ac53d38
updating for base changes
SamuelBellomo Jun 21, 2021
a25a832
Merge branch 'develop' into feature/adding-tests-for-network-transform
SamuelBellomo Jun 29, 2021
41362e7
Merge branch 'develop' into feature/adding-tests-for-network-transform
SamuelBellomo Jun 29, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 39 additions & 15 deletions com.unity.multiplayer.mlapi/Prototyping/NetworkTransform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public enum Authority
/// <summary>
/// Sets whether this transform should sync local or world properties. This is important to set since reparenting this transform
/// could have issues if using world position (depending on who gets synced first: the parent or the child)
/// Having a child always at position 0,0,0 for example will have less possibilities of desync than when using world positions
/// </summary>
[SerializeField, Tooltip("Sets whether this transform should sync local or world properties. This should be set if reparenting.")]
private NetworkVariableBool m_UseLocal = new NetworkVariableBool();
Expand All @@ -99,10 +100,46 @@ public bool UseLocal
set => m_UseLocal.Value = value;
}

/// <summary>
/// Updates the NetworkTransform's authority model at runtime.
/// </summary>
/// <param name="newAuthority"></param>
public void SetAuthority(Authority newAuthority)
{
TransformAuthority = newAuthority;
UpdateVarPermissions();
// todo this should be synced with the other side. let's wait for a more final solution before adding more code here
}

private void UpdateOneVarPermission<T>(NetworkVariable<T> varToUpdate)
{
switch (TransformAuthority)
{
case Authority.Client:
varToUpdate.Settings.WritePermission = NetworkVariablePermission.OwnerOnly;
break;
case Authority.Shared:
varToUpdate.Settings.WritePermission = NetworkVariablePermission.Everyone;
break;
case Authority.Server:
m_NetworkPosition.Settings.WritePermission = NetworkVariablePermission.ServerOnly;
break;
default:
throw new NotImplementedException($"{TransformAuthority} is not handled");
}
}

private void UpdateVarPermissions()
{
UpdateOneVarPermission(m_NetworkPosition);
UpdateOneVarPermission(m_NetworkRotation);
UpdateOneVarPermission(m_NetworkWorldScale);
UpdateOneVarPermission(m_UseLocal);
}

private NetworkVariableVector3 m_NetworkPosition = new NetworkVariableVector3();
private NetworkVariableQuaternion m_NetworkRotation = new NetworkVariableQuaternion();
private NetworkVariableVector3 m_NetworkWorldScale = new NetworkVariableVector3();
// private NetworkTransform m_NetworkParent; // TODO handle this here?

private Transform m_Transform;

Expand Down Expand Up @@ -197,20 +234,7 @@ void SetupVar<T>(NetworkVariable<T> v, T initialValue, ref T oldVal)
SetupVar(m_NetworkRotation, m_CurrentRotation, ref m_OldRotation);
SetupVar(m_NetworkWorldScale, m_CurrentScale, ref m_OldScale);

if (TransformAuthority == Authority.Client)
{
m_NetworkPosition.Settings.WritePermission = NetworkVariablePermission.OwnerOnly;
m_NetworkRotation.Settings.WritePermission = NetworkVariablePermission.OwnerOnly;
m_NetworkWorldScale.Settings.WritePermission = NetworkVariablePermission.OwnerOnly;
m_UseLocal.Settings.WritePermission = NetworkVariablePermission.OwnerOnly;
}
else if (TransformAuthority == Authority.Shared)
{
m_NetworkPosition.Settings.WritePermission = NetworkVariablePermission.Everyone;
m_NetworkRotation.Settings.WritePermission = NetworkVariablePermission.Everyone;
m_NetworkWorldScale.Settings.WritePermission = NetworkVariablePermission.Everyone;
m_UseLocal.Settings.WritePermission = NetworkVariablePermission.Everyone;
}
UpdateVarPermissions();
}

public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public NetworkPrefabHandler PrefabHandler
public ulong ServerClientId => NetworkConfig.NetworkTransport?.ServerClientId ?? throw new NullReferenceException($"The transport in the active {nameof(NetworkConfig)} is null");

/// <summary>
/// The clientId the server calls the local client by, only valid for clients
/// Returns ServerClientId if IsServer or LocalClientId if not
/// </summary>
public ulong LocalClientId
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ private class CoroutineRunner : MonoBehaviour
private static CoroutineRunner s_CoroutineRunner;

/// <summary>
/// Runs a IEnumerator as a Coroutine on a dummy GameObject.
/// Runs a IEnumerator as a Coroutine on a dummy GameObject. Used to get exceptions coming from the coroutine
/// </summary>
/// <param name="enumerator">The IEnumerator to run</param>
public static Coroutine Run(IEnumerator enumerator)
Expand Down
151 changes: 151 additions & 0 deletions com.unity.multiplayer.mlapi/Tests/Runtime/NetworkTransformTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
using System;
using System.Collections;
using System.Text.RegularExpressions;
using MLAPI.Prototyping;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using static MLAPI.Prototyping.NetworkTransform;

namespace MLAPI.RuntimeTests
{
[TestFixture(true)]
[TestFixture(false)]
Comment thread
SamuelBellomo marked this conversation as resolved.
public class NetworkTransformTests : BaseMultiInstanceTest
Comment thread
SamuelBellomo marked this conversation as resolved.
{
private NetworkObject m_ClientSideClientPlayer;
private NetworkObject m_ServerSideClientPlayer;

private bool m_TestWithHost;

public NetworkTransformTests(bool testWithHost)
{
m_TestWithHost = testWithHost; // from test fixture
}

protected override int NbClients => 1;

[UnitySetUp]
public override IEnumerator Setup()
{
yield return StartSomeClientsAndServerWithPlayers(useHost: m_TestWithHost, nbClients: NbClients, updatePlayerPrefab: playerPrefab =>
{
var networkTransform = playerPrefab.AddComponent<NetworkTransform>();
});

// This is the *SERVER VERSION* of the *CLIENT PLAYER*
var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult));

// This is the *CLIENT VERSION* of the *CLIENT PLAYER*
var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult));

m_ServerSideClientPlayer = serverClientPlayerResult.Result;
m_ClientSideClientPlayer = clientClientPlayerResult.Result;
}

[UnityTest]
[TestCase(true, Authority.Client, ExpectedResult = null)]
[TestCase(true, Authority.Server, ExpectedResult = null)]
[TestCase(false, Authority.Client, ExpectedResult = null)]
[TestCase(false, Authority.Server, ExpectedResult = null)]
public IEnumerator TestAuthoritativeTransformChangeOneAtATime(bool testLocalTransform, Authority authorityToTest)
{
var waitResult = new MultiInstanceHelpers.CoroutineResultWrapper<bool>();

var networkTransform = (authorityToTest == Authority.Client ? m_ClientSideClientPlayer : m_ServerSideClientPlayer).GetComponent<NetworkTransform>();
networkTransform.SetAuthority(authorityToTest);

var otherSideNetworkTransform = (authorityToTest == Authority.Client ? m_ServerSideClientPlayer : m_ClientSideClientPlayer).GetComponent<NetworkTransform>();
otherSideNetworkTransform.SetAuthority(authorityToTest);

bool HasAuthority(NetworkTransform transform)
{
return transform.NetworkObject.NetworkManager.IsServer && transform.TransformAuthority == Authority.Server ||
transform.NetworkObject.NetworkManager.IsClient && transform.TransformAuthority == Authority.Client;
}

if (HasAuthority(networkTransform))
{
networkTransform.UseLocal = testLocalTransform;
}

if (HasAuthority(otherSideNetworkTransform))
{
otherSideNetworkTransform.UseLocal = testLocalTransform;
}

float approximation = 0.05f;

// test position
var playerTransform = networkTransform.transform;
playerTransform.position = new Vector3(10, 20, 30);
Assert.AreEqual(Vector3.zero, otherSideNetworkTransform.transform.position, "server side pos should be zero at first"); // sanity check
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForCondition(() => otherSideNetworkTransform.transform.position.x > approximation, waitResult, maxFrames: 30));
if (!waitResult.Result)
{
throw new Exception("timeout while waiting for position change");
}
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
playerTransform.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 MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForCondition(() => otherSideNetworkTransform.transform.rotation.eulerAngles.x > approximation, waitResult, maxFrames: 30));
if (!waitResult.Result)
{
throw new Exception("timeout while waiting for position 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
playerTransform.localScale = new Vector3(2, 3, 4);
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForCondition(() => otherSideNetworkTransform.transform.lossyScale.x > 1f + approximation, waitResult, maxFrames: 30));
if (!waitResult.Result)
{
throw new Exception("timeout while waiting for position 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]
[TestCase(Authority.Client, ExpectedResult = null)]
[TestCase(Authority.Server, ExpectedResult = null)]
public IEnumerator TestCantChangeTransformFromOtherSideAuthority(Authority authorityToTest)
{
// test server can't change client authoritative transform
var networkTransform = (authorityToTest == Authority.Client ? m_ClientSideClientPlayer : m_ServerSideClientPlayer).GetComponent<NetworkTransform>();
networkTransform.SetAuthority(authorityToTest);

var otherSideNetworkTransform = (authorityToTest == Authority.Client ? m_ServerSideClientPlayer : m_ClientSideClientPlayer).GetComponent<NetworkTransform>();
otherSideNetworkTransform.SetAuthority(authorityToTest);

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 new WaitForFixedUpdate();

LogAssert.Expect(LogType.Error, new Regex(".*[Aa]uthority.*"));
Assert.AreEqual(Vector3.zero, otherSideNetworkTransform.transform.position, "got authority error, but other side still moved!");
}

[UnityTearDown]
public override IEnumerator Teardown()
{
yield return base.Teardown();
UnityEngine.Object.Destroy(m_PlayerPrefab);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"name": "Unity.Multiplayer.MLAPI.RuntimeTests",
"references": [
"Unity.Multiplayer.MLAPI.Runtime",
"Unity.Multiplayer.MLAPI.Editor"
"Unity.Multiplayer.MLAPI.Editor",
"Unity.Multiplayer.MLAPI.Prototyping"
],
"optionalUnityReferences": [
"TestAssemblies"
Expand Down
2 changes: 2 additions & 0 deletions testproject/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,5 @@ crashlytics-build.properties
# Temporary auto-generated Android Assets
/[Aa]ssets/[Ss]treamingAssets/aa.meta
/[Aa]ssets/[Ss]treamingAssets/aa/*

InitTestScene*
Comment thread
SamuelBellomo marked this conversation as resolved.