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: