diff --git a/com.unity.multiplayer.mlapi/Runtime/Serialization/NetworkWriter.cs b/com.unity.multiplayer.mlapi/Runtime/Serialization/NetworkWriter.cs index cf04723473..e9961fc6d4 100644 --- a/com.unity.multiplayer.mlapi/Runtime/Serialization/NetworkWriter.cs +++ b/com.unity.multiplayer.mlapi/Runtime/Serialization/NetworkWriter.cs @@ -232,8 +232,7 @@ public void WriteObjectPacked(object value) return; } - - throw new ArgumentException($"{nameof(NetworkWriter)} cannot write type {value.GetType()}"); + throw new ArgumentException($"{nameof(NetworkWriter)} cannot write type {value.GetType().Name} - it does not implement {nameof(INetworkSerializable)}"); } /// diff --git a/com.unity.multiplayer.mlapi/Tests/Runtime/MultiInstanceHelpers.cs b/com.unity.multiplayer.mlapi/Tests/Runtime/MultiInstanceHelpers.cs index f3297b6ccd..4063cc52c7 100644 --- a/com.unity.multiplayer.mlapi/Tests/Runtime/MultiInstanceHelpers.cs +++ b/com.unity.multiplayer.mlapi/Tests/Runtime/MultiInstanceHelpers.cs @@ -431,6 +431,29 @@ public static IEnumerator GetNetworkObjectByRepresentation(Func + /// Runs some code, then verifies the condition (combines 'Run' and 'WaitForCondition') + /// + /// Action / code to run + /// The predicate to wait for + /// The max frames to wait for + public static IEnumerator RunAndWaitForCondition(Action workload, Func predicate, int maxFrames = 64) + { + var waitResult = new CoroutineResultWrapper(); + workload(); + + yield return Run(WaitForCondition( + predicate, + waitResult, + maxFrames: maxFrames)); + + if (!waitResult.Result) + { + throw new Exception(); + } + } + + /// /// Waits for a predicate condition to be met /// diff --git a/com.unity.multiplayer.mlapi/Tests/Runtime/NetworkVariableTests.cs b/com.unity.multiplayer.mlapi/Tests/Runtime/NetworkVariableTests.cs index f64e97915c..dd3cde9001 100644 --- a/com.unity.multiplayer.mlapi/Tests/Runtime/NetworkVariableTests.cs +++ b/com.unity.multiplayer.mlapi/Tests/Runtime/NetworkVariableTests.cs @@ -1,26 +1,146 @@ using System; using System.Collections; +using System.Collections.Generic; using UnityEngine; using UnityEngine.TestTools; using NUnit.Framework; +using Unity.Netcode; + +public struct TestStruct : INetworkSerializable +{ + public uint SomeInt; + public bool SomeBool; + + public void NetworkSerialize(NetworkSerializer serializer) + { + serializer.Serialize(ref SomeInt); + serializer.Serialize(ref SomeBool); + } +} + +public class TestClass : INetworkSerializable +{ + public uint SomeInt; + public bool SomeBool; + + public void NetworkSerialize(NetworkSerializer serializer) + { + serializer.Serialize(ref SomeInt); + serializer.Serialize(ref SomeBool); + } +} +public class NetworkVariableTest : NetworkBehaviour +{ + public readonly NetworkList TheList = new NetworkList( + new NetworkVariableSettings {WritePermission = NetworkVariablePermission.ServerOnly} + ); + + public readonly NetworkSet TheSet = new NetworkSet( + new NetworkVariableSettings {WritePermission = NetworkVariablePermission.ServerOnly} + ); + + public readonly NetworkDictionary TheDictionary = new NetworkDictionary( + new NetworkVariableSettings {WritePermission = NetworkVariablePermission.ServerOnly} + ); + + private void ListChanged(NetworkListEvent e) + { + ListDelegateTriggered = true; + } + private void SetChanged(NetworkSetEvent e) + { + SetDelegateTriggered = true; + } + private void DictionaryChanged(NetworkDictionaryEvent e) + { + DictionaryDelegateTriggered = true; + } + public void Awake() + { + TheList.OnListChanged += ListChanged; + TheSet.OnSetChanged += SetChanged; + TheDictionary.OnDictionaryChanged += DictionaryChanged; + } + + public readonly NetworkVariable TheStruct = new NetworkVariable(); + public readonly NetworkVariable TheClass = new NetworkVariable(new TestClass()); + + public bool ListDelegateTriggered; + public bool SetDelegateTriggered; + public bool DictionaryDelegateTriggered; +} namespace Unity.Netcode.RuntimeTests { - public class NetworkVariableTests + public class NetworkVariableTests : BaseMultiInstanceTest { - [SetUp] - public void Setup() + protected override int NbClients => 1; + + private const uint k_TestUInt = 0xdeadbeef; + + private const int k_TestVal1 = 111; + private const int k_TestVal2 = 222; + private const int k_TestVal3 = 333; + + private const int k_TestKey1 = 0x0f0f; + private const int k_TestKey2 = 0xf0f0; + + private NetworkVariableTest m_ServerComp; + private NetworkVariableTest m_ClientComp; + + private readonly bool m_TestWithHost = false; + + [UnitySetUp] + public override IEnumerator Setup() { - // Create, instantiate, and host - Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _)); + yield return StartSomeClientsAndServerWithPlayers(useHost: m_TestWithHost, nbClients: NbClients, + updatePlayerPrefab: playerPrefab => + { + var networkTransform = playerPrefab.AddComponent(); + }); + + // This is the *SERVER VERSION* of the *CLIENT PLAYER* + var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); + 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(); + yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation( + x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId, + m_ClientNetworkManagers[0], clientClientPlayerResult)); + + var serverSideClientPlayer = serverClientPlayerResult.Result; + var clientSideClientPlayer = clientClientPlayerResult.Result; + + m_ServerComp = serverSideClientPlayer.GetComponent(); + m_ClientComp = clientSideClientPlayer.GetComponent(); + + m_ServerComp.TheList.Clear(); + + if (m_ServerComp.TheList.Count > 0) + { + throw new Exception("server network list not empty at start"); + } + if (m_ClientComp.TheList.Count > 0) + { + throw new Exception("client network list not empty at start"); + } } /// /// Runs generalized tests on all predefined NetworkVariable types /// [UnityTest] - public IEnumerator TestAllNetworkVariableTypes() + public IEnumerator AllNetworkVariableTypes() { + // Create, instantiate, and host + // This would normally go in Setup, but since every other test but this one + // uses MultiInstanceHelper, and it does its own NetworkManager setup / teardown, + // for now we put this within this one test until we migrate it to MIH + Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _)); + Guid gameObjectId = NetworkManagerHelper.AddGameNetworkObject("NetworkVariableTestComponent"); var networkVariableTestComponent = NetworkManagerHelper.AddComponentToObject(gameObjectId); @@ -48,13 +168,284 @@ public IEnumerator TestAllNetworkVariableTypes() networkVariableTestComponent.gameObject.SetActive(false); Assert.IsTrue(testsAreComplete); + + // This would normally go in Teardown, but since every other test but this one + // uses MultiInstanceHelper, and it does its own NetworkManager setup / teardown, + // for now we put this within this one test until we migrate it to MIH + NetworkManagerHelper.ShutdownNetworkManager(); } - [TearDown] - public void TearDown() + [UnityTest] + public IEnumerator NetworkListAdd() { - // Stop, shutdown, and destroy - NetworkManagerHelper.ShutdownNetworkManager(); + var waitResult = new MultiInstanceHelpers.CoroutineResultWrapper(); + + yield return MultiInstanceHelpers.RunAndWaitForCondition( + () => + { + m_ServerComp.TheList.Add(k_TestVal1); + m_ServerComp.TheList.Add(k_TestVal2); + }, + () => + { + return m_ServerComp.TheList.Count == 2 && + m_ClientComp.TheList.Count == 2 && + m_ServerComp.ListDelegateTriggered && + m_ClientComp.ListDelegateTriggered && + m_ServerComp.TheList[0] == k_TestVal1 && + m_ClientComp.TheList[0] == k_TestVal1 && + m_ServerComp.TheList[1] == k_TestVal2 && + m_ClientComp.TheList[1] == k_TestVal2; + } + ); + } + + [UnityTest] + public IEnumerator NetworkListRemove() + { + // first put some stuff in; re-use the add test + yield return NetworkListAdd(); + + yield return MultiInstanceHelpers.RunAndWaitForCondition( + () => m_ServerComp.TheList.RemoveAt(0), + () => + { + return m_ServerComp.TheList.Count == 1 && + m_ClientComp.TheList.Count == 1 && + m_ServerComp.ListDelegateTriggered && + m_ClientComp.ListDelegateTriggered && + m_ServerComp.TheList[0] == k_TestVal2 && + m_ClientComp.TheList[0] == k_TestVal2; + } + ); + } + + [UnityTest] + public IEnumerator NetworkListClear() + { + // first put some stuff in; re-use the add test + yield return NetworkListAdd(); + + yield return MultiInstanceHelpers.RunAndWaitForCondition( + () => m_ServerComp.TheList.Clear(), + () => + { + return + m_ServerComp.ListDelegateTriggered && + m_ClientComp.ListDelegateTriggered && + m_ServerComp.TheList.Count == 0 && + m_ClientComp.TheList.Count == 0; + } + ); + } + + [UnityTest] + public IEnumerator NetworkSetAdd() + { + yield return MultiInstanceHelpers.RunAndWaitForCondition( + () => + { + ISet iSet = m_ServerComp.TheSet; + iSet.Add(k_TestVal1); + iSet.Add(k_TestVal2); + }, + () => + { + return m_ServerComp.TheSet.Count == 2 && + m_ClientComp.TheSet.Count == 2 && + m_ServerComp.SetDelegateTriggered && + m_ClientComp.SetDelegateTriggered && + m_ServerComp.TheSet.Contains(k_TestVal1) && + m_ClientComp.TheSet.Contains(k_TestVal1) && + m_ServerComp.TheSet.Contains(k_TestVal2) && + m_ClientComp.TheSet.Contains(k_TestVal2); + } + ); + } + + [UnityTest] + public IEnumerator NetworkSetRemove() + { + // first put some stuff in; re-use the add test + yield return NetworkSetAdd(); + + yield return MultiInstanceHelpers.RunAndWaitForCondition( + () => + { + ISet iSet = m_ServerComp.TheSet; + iSet.Remove(k_TestVal1); + }, + () => + { + return m_ServerComp.TheSet.Count == 1 && + m_ClientComp.TheSet.Count == 1 && + m_ServerComp.SetDelegateTriggered && + m_ClientComp.SetDelegateTriggered && + m_ServerComp.TheSet.Contains(k_TestVal2) && + m_ClientComp.TheSet.Contains(k_TestVal2); + } + ); + } + + [UnityTest] + public IEnumerator NetworkSetClear() + { + // first put some stuff in; re-use the add test + yield return NetworkSetAdd(); + + yield return MultiInstanceHelpers.RunAndWaitForCondition( + () => + { + ISet iSet = m_ServerComp.TheSet; + iSet.Clear(); + }, + () => + { + return m_ServerComp.TheSet.Count == 0 && + m_ClientComp.TheSet.Count == 0 && + m_ServerComp.SetDelegateTriggered && + m_ClientComp.SetDelegateTriggered; + } + ); + } + + [UnityTest] + public IEnumerator NetworkDictionaryAdd() + { + yield return MultiInstanceHelpers.RunAndWaitForCondition( + () => + { + m_ServerComp.TheDictionary.Add(k_TestKey1, k_TestVal1); + m_ServerComp.TheDictionary.Add(k_TestKey2, k_TestVal2); + }, + () => + { + return m_ServerComp.TheDictionary.Count == 2 && + m_ClientComp.TheDictionary.Count == 2 && + m_ServerComp.DictionaryDelegateTriggered && + m_ClientComp.DictionaryDelegateTriggered && + m_ServerComp.TheDictionary[k_TestKey1] == k_TestVal1 && + m_ClientComp.TheDictionary[k_TestKey1] == k_TestVal1 && + m_ServerComp.TheDictionary[k_TestKey2] == k_TestVal2 && + m_ClientComp.TheDictionary[k_TestKey2] == k_TestVal2; + } + ); + } + + /* Note, not adding coverage for RemovePair, because we plan to remove + * this in the next PR + */ + [UnityTest] + public IEnumerator NetworkDictionaryRemoveByKey() + { + // first put some stuff in; re-use the add test + yield return NetworkDictionaryAdd(); + + yield return MultiInstanceHelpers.RunAndWaitForCondition( + () => + { + m_ServerComp.TheDictionary.Remove(k_TestKey2); + }, + () => + { + return m_ServerComp.TheDictionary.Count == 1 && + m_ClientComp.TheDictionary.Count == 1 && + m_ServerComp.DictionaryDelegateTriggered && + m_ClientComp.DictionaryDelegateTriggered && + m_ServerComp.TheDictionary[k_TestKey1] == k_TestVal1 && + m_ClientComp.TheDictionary[k_TestKey1] == k_TestVal1; + } + ); + } + + [UnityTest] + public IEnumerator NetworkDictionaryChangeValue() + { + // first put some stuff in; re-use the add test + yield return NetworkDictionaryAdd(); + + yield return MultiInstanceHelpers.RunAndWaitForCondition( + () => + { + m_ServerComp.TheDictionary[k_TestKey1] = k_TestVal3; + }, + () => + { + return m_ServerComp.TheDictionary.Count == 2 && + m_ClientComp.TheDictionary.Count == 2 && + m_ServerComp.DictionaryDelegateTriggered && + m_ClientComp.DictionaryDelegateTriggered && + m_ServerComp.TheDictionary[k_TestKey1] == k_TestVal3 && + m_ClientComp.TheDictionary[k_TestKey1] == k_TestVal3; + } + ); + } + + [UnityTest] + public IEnumerator NetworkDictionaryClear() + { + // first put some stuff in; re-use the add test + yield return NetworkDictionaryAdd(); + + yield return MultiInstanceHelpers.RunAndWaitForCondition( + () => + { + m_ServerComp.TheDictionary.Clear(); + }, + () => + { + return m_ServerComp.TheDictionary.Count == 0 && + m_ClientComp.TheDictionary.Count == 0 && + m_ServerComp.DictionaryDelegateTriggered && + m_ClientComp.DictionaryDelegateTriggered; + } + ); + } + + [UnityTest] + public IEnumerator TestNetworkVariableClass() + { + yield return MultiInstanceHelpers.RunAndWaitForCondition( + () => + { + m_ServerComp.TheClass.Value.SomeBool = false; + m_ServerComp.TheClass.Value.SomeInt = k_TestUInt; + m_ServerComp.TheClass.SetDirty(true); + }, + () => + { + return + m_ClientComp.TheClass.Value.SomeBool == false && + m_ClientComp.TheClass.Value.SomeInt == k_TestUInt; + } + ); + } + + [UnityTest] + public IEnumerator TestNetworkVariableStruct() + { + yield return MultiInstanceHelpers.RunAndWaitForCondition( + () => + { + m_ServerComp.TheStruct.Value = + new TestStruct() {SomeInt = k_TestUInt, SomeBool = false}; + m_ServerComp.TheStruct.SetDirty(true); + }, + () => + { + return + m_ClientComp.TheStruct.Value.SomeBool == false && + m_ClientComp.TheStruct.Value.SomeInt == k_TestUInt; + } + ); + } + + + [UnityTearDown] + public override IEnumerator Teardown() + { + yield return base.Teardown(); + UnityEngine.Object.Destroy(m_PlayerPrefab); } } } diff --git a/testproject/Assets/Tests/Manual/Scripts/ManualNetworkVariableTest.cs b/testproject/Assets/Tests/Manual/Scripts/ManualNetworkVariableTest.cs deleted file mode 100644 index 4ea73ddc77..0000000000 --- a/testproject/Assets/Tests/Manual/Scripts/ManualNetworkVariableTest.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; -using Unity.Netcode; - -namespace TestProject.ManualTests -{ - /// - /// A prototype component for syncing transforms - /// - [AddComponentMenu("MLAPI/ManualNetworkVariableTest")] - public class ManualNetworkVariableTest : NetworkBehaviour - { - // testing NetworkList - private NetworkList m_TestList = new NetworkList(); - private bool m_GotNetworkList = false; - - // testing NetworkSet - private NetworkSet m_TestSet = new NetworkSet(); - private bool m_GotNetworkSet = false; - - // testing NetworkDictionary - private NetworkDictionary m_TestDictionary = new NetworkDictionary(); - private bool m_GotNetworkDictionary = false; - - // testing NetworkVariable, especially ticks - private NetworkVariable m_TestVar = new NetworkVariable(); - - private string m_Problems = string.Empty; - private bool m_Started = false; - - private const int k_EndValue = 1000; - - private void Start() - { - m_TestList.SetNetworkBehaviour(this); - m_TestSet.SetNetworkBehaviour(this); - m_TestDictionary.SetNetworkBehaviour(this); - - m_TestVar.OnValueChanged += ValueChanged; - m_TestVar.Settings.WritePermission = NetworkVariablePermission.Everyone; - - m_TestList.OnListChanged += ListChanged; - m_TestList.Settings.WritePermission = NetworkVariablePermission.OwnerOnly; - - m_TestSet.OnSetChanged += SetChanged; - m_TestSet.Settings.WritePermission = NetworkVariablePermission.OwnerOnly; - - m_TestDictionary.OnDictionaryChanged += DictionaryChanged; - m_TestDictionary.Settings.WritePermission = NetworkVariablePermission.OwnerOnly; - - if (IsOwner) - { - m_TestVar.Value = 0; - Debug.Log("We'll be sending " + MyMessage()); - } - } - - private void FixedUpdate() - { - if (!m_Started && NetworkManager.ConnectedClientsList.Count > 0) - { - m_Started = true; - } - if (m_Started) - { - m_TestVar.Value = m_TestVar.Value + 1; - m_TestList.Add(MyMessage()); - ((ICollection)m_TestSet).Add(MyMessage()); - m_TestDictionary[0] = MyMessage(); - } - } - - private string MyMessage() - { - return "Message from " + NetworkObjectId; - } - - private void ListChanged(NetworkListEvent listEvent) - { - if (!IsOwner && !m_GotNetworkList) - { - Debug.Log("Received: " + listEvent.Value); - m_GotNetworkList = true; - } - } - - private void SetChanged(NetworkSetEvent setEvent) - { - if (!IsOwner && !m_GotNetworkSet) - { - Debug.Log("Received: " + setEvent.Value); - m_GotNetworkSet = true; - } - } - - private void DictionaryChanged(NetworkDictionaryEvent dictionaryEvent) - { - if (!IsOwner && !m_GotNetworkSet) - { - Debug.Log("Received: " + dictionaryEvent.Key + ":" + dictionaryEvent.Value); - m_GotNetworkDictionary = true; - } - } - - private void ValueChanged(int before, int after) - { - if (!IsOwner && !IsServer && m_TestVar.Value >= k_EndValue) - { - // Let's be reasonable and allow a 5 tick difference - // that could be due to timing difference, lag, queueing - - if (!m_GotNetworkList) - { - m_Problems += "Didn't receive any NetworkList updates from other machines"; - } - - if (!m_GotNetworkSet) - { - m_Problems += "Didn't receive any NetworkSet updates from other machines"; - } - - if (!m_GotNetworkDictionary) - { - m_Problems += "Didn't receive any NetworkDictionary updates from other machines"; - } - - if (m_Problems == "") - { - Debug.Log("**** TEST PASSED ****"); - } - else - { - Debug.Log("**** TEST FAILED ****"); - Debug.Log(m_Problems); - } - } - - if (m_TestVar.Value >= k_EndValue) - { - enabled = false; - } - } - } -} diff --git a/testproject/Assets/Tests/Manual/Scripts/ManualNetworkVariableTest.cs.meta b/testproject/Assets/Tests/Manual/Scripts/ManualNetworkVariableTest.cs.meta deleted file mode 100644 index db46c0dac0..0000000000 --- a/testproject/Assets/Tests/Manual/Scripts/ManualNetworkVariableTest.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 962d0654df408407d8055453c9020f2b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: