diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md
index 5c5772143f..d1f9893832 100644
--- a/com.unity.netcode.gameobjects/CHANGELOG.md
+++ b/com.unity.netcode.gameobjects/CHANGELOG.md
@@ -11,6 +11,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Added
- Clicking on the Help icon in the inspector will now redirect to the relevant documentation. (#3663)
+- Added a `Set` function onto `NetworkList` that takes an optional parameter that forces an update to be processed even if the current value is equal to the previous value. (#3690)
### Changed
diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs
index c7c2554d21..c4b6a0e803 100644
--- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs
+++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Runtime.CompilerServices;
using Unity.Collections;
namespace Unity.Netcode
@@ -597,45 +598,60 @@ public void RemoveAt(int index)
}
///
- /// Gets or sets the element at the specified index in the .
+ /// Sets the element at the specified index in the .
///
///
- /// This method checks for write permissions before setting the value.
+ /// This method checks for write permissions and equality before setting and updating the value.
///
- /// The zero-based index of the element to get or set.
- /// The element at the specified index.
- public T this[int index]
+ /// The zero-based index of the element to set.
+ /// The new value to set at the given index
+ ///
+ /// Ignores the equality check when setting the value.
+ /// This option can send unnecessary updates to all clients when the value hasn't changed.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Set(int index, T value, bool forceUpdate = false)
{
- get => m_List[index];
- set
+ // check write permissions
+ if (CannotWrite)
{
- // check write permissions
- if (CannotWrite)
- {
- LogWritePermissionError();
- return;
- }
+ LogWritePermissionError();
+ return;
+ }
- var previousValue = m_List[index];
+ var previousValue = m_List[index];
- // Only trigger an event if the value has changed
- if (NetworkVariableSerialization.AreEqual(ref previousValue, ref value))
- {
- return;
- }
+ // Only trigger an event if the value has changed
+ if (!forceUpdate && NetworkVariableSerialization.AreEqual(ref previousValue, ref value))
+ {
+ return;
+ }
- m_List[index] = value;
+ m_List[index] = value;
- var listEvent = new NetworkListEvent()
- {
- Type = NetworkListEvent.EventType.Value,
- Index = index,
- Value = value,
- PreviousValue = previousValue
- };
+ var listEvent = new NetworkListEvent()
+ {
+ Type = NetworkListEvent.EventType.Value,
+ Index = index,
+ Value = value,
+ PreviousValue = previousValue
+ };
- HandleAddListEvent(listEvent);
- }
+ HandleAddListEvent(listEvent);
+ }
+
+ ///
+ /// Gets or sets the element at the specified index in the .
+ ///
+ ///
+ /// This method checks for write permissions before setting the value.
+ ///
+ /// The zero-based index of the element to get or set.
+ /// The element at the specified index.
+ public T this[int index]
+ {
+ get => m_List[index];
+ set => Set(index, value);
}
///
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkListTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkListTests.cs
new file mode 100644
index 0000000000..15c737fbc3
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkListTests.cs
@@ -0,0 +1,410 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using NUnit.Framework;
+using Unity.Collections;
+using Unity.Netcode.TestHelpers.Runtime;
+using UnityEngine;
+using UnityEngine.TestTools;
+using Random = UnityEngine.Random;
+
+namespace Unity.Netcode.RuntimeTests
+{
+ [TestFixture(HostOrServer.Host)]
+ [TestFixture(HostOrServer.DAHost)]
+ [TestFixture(HostOrServer.Server)]
+ internal class NetworkListTests : NetcodeIntegrationTest
+ {
+ protected override int NumberOfClients => 3;
+
+ public NetworkListTests(HostOrServer host) : base(host) { }
+
+ private GameObject m_ListObjectPrefab;
+
+ private List m_ExpectedValues = new();
+
+ private ulong m_TestObjectId;
+
+ protected override void OnServerAndClientsCreated()
+ {
+ m_ListObjectPrefab = CreateNetworkObjectPrefab("ListObject");
+ m_ListObjectPrefab.AddComponent();
+
+ base.OnServerAndClientsCreated();
+ }
+
+ private bool OnVerifyData(StringBuilder errorLog)
+ {
+ foreach (var manager in m_NetworkManagers)
+ {
+ if (!manager.SpawnManager.SpawnedObjects.TryGetValue(m_TestObjectId, out NetworkObject networkObject))
+ {
+ errorLog.Append($"[Client-{manager.LocalClientId}] Test object was not spawned");
+ return false;
+ }
+
+ var listComponent = networkObject.GetComponent();
+ if (listComponent == null)
+ {
+ errorLog.Append($"[Client-{manager.LocalClientId}] List component was not found");
+ return false;
+ }
+
+ if (m_ExpectedValues.Count != listComponent.TheList.Count)
+ {
+ errorLog.Append($"[Client-{manager.LocalClientId}] List component has the incorrect number of items. Expected: {m_ExpectedValues.Count}, Have: {listComponent.TheList.Count}");
+ return false;
+ }
+
+ for (int i = 0; i < m_ExpectedValues.Count; i++)
+ {
+ var expectedValue = m_ExpectedValues[i];
+ var actual = listComponent.TheList[i];
+
+ if (expectedValue != actual)
+ {
+ errorLog.Append($"[Client-{manager.LocalClientId}] Incorrect value at index {i}, expected: {expectedValue}, actual: {actual}");
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ [UnityTest]
+ public IEnumerator ValidateNetworkListSynchronization()
+ {
+ var authority = GetAuthorityNetworkManager();
+ var nonAuthority = GetNonAuthorityNetworkManager();
+ var instantiatedObject = SpawnObject(m_ListObjectPrefab, authority).GetComponent();
+ m_TestObjectId = instantiatedObject.NetworkObjectId;
+
+ yield return WaitForSpawnedOnAllOrTimeOut(instantiatedObject);
+ AssertOnTimeout("[Setup] Failed to spawn list object");
+
+ Assert.IsTrue(nonAuthority.SpawnManager.SpawnedObjects.TryGetValue(instantiatedObject.NetworkObjectId, out var nonAuthorityObject));
+
+ var authorityInstance = instantiatedObject.GetComponent();
+ var nonAuthorityInstance = nonAuthorityObject.GetComponent();
+
+ m_ExpectedValues.Clear();
+
+ // NetworkList.Add
+ for (int i = 0; i < 10; i++)
+ {
+ var val = Random.Range(0, 1234);
+ m_ExpectedValues.Add(val);
+
+ authorityInstance.TheList.Add(val);
+ }
+
+ yield return WaitForConditionOrTimeOut(OnVerifyData);
+ AssertOnTimeout("[Add] Failed to add items to list");
+
+ // NetworkList.Contains
+ foreach (var expectedValue in m_ExpectedValues)
+ {
+ Assert.IsTrue(authorityInstance.TheList.Contains(expectedValue), $"[Contains][Client-{authority.LocalClientId}] Does not contain {expectedValue}");
+ Assert.IsTrue(nonAuthorityInstance.TheList.Contains(expectedValue), $"[Contains][Client-{nonAuthority.LocalClientId}] Does not contain {expectedValue}");
+ }
+
+ // NetworkList.Insert
+ for (int i = 0; i < 5; i++)
+ {
+ var indexToInsert = Random.Range(0, m_ExpectedValues.Count);
+ var valToInsert = Random.Range(1, 99);
+ m_ExpectedValues.Insert(indexToInsert, valToInsert);
+
+ authorityInstance.TheList.Insert(indexToInsert, valToInsert);
+ }
+
+ yield return WaitForConditionOrTimeOut(OnVerifyData);
+ AssertOnTimeout("[Insert] Failed to insert items to list");
+
+
+ // NetworkList.IndexOf
+ foreach (var testValue in Shuffle(m_ExpectedValues))
+ {
+ var expectedIndex = m_ExpectedValues.IndexOf(testValue);
+
+ Assert.AreEqual(expectedIndex, authorityInstance.TheList.IndexOf(testValue), $"[IndexOf][Client-{authority.LocalClientId}] Has incorrect index for {testValue}");
+ Assert.AreEqual(expectedIndex, nonAuthorityInstance.TheList.IndexOf(testValue), $"[IndexOf][Client-{nonAuthority.LocalClientId}] Has incorrect index for {testValue}");
+ }
+
+ // NetworkList[index] getter and setter
+ foreach (var testValue in Shuffle(m_ExpectedValues))
+ {
+ var testIndex = m_ExpectedValues.IndexOf(testValue);
+
+ // Set up our original and previous
+ var previousValue = testValue;
+ var updatedValue = previousValue + 10;
+
+ m_ExpectedValues[testIndex] = updatedValue;
+
+ Assert.AreEqual(testValue, authorityInstance.TheList[testIndex], $"[Get][Client-{authority.LocalClientId}] incorrect index get");
+ Assert.AreEqual(testValue, nonAuthorityInstance.TheList[testIndex], $"[Get][Client-{nonAuthority.LocalClientId}] incorrect index get");
+
+ var callbackSucceeded = false;
+
+ // Callback that verifies the changed event occurred and that the original and new values are correct
+ void TestValueUpdatedCallback(NetworkListEvent changedEvent)
+ {
+ nonAuthorityInstance.TheList.OnListChanged -= TestValueUpdatedCallback;
+
+ callbackSucceeded = changedEvent.PreviousValue == previousValue &&
+ changedEvent.Value == updatedValue;
+ }
+
+ // Subscribe to the OnListChanged event on the client side and
+ nonAuthorityInstance.TheList.OnListChanged += TestValueUpdatedCallback;
+ authorityInstance.TheList[testIndex] = updatedValue;
+
+ yield return WaitForConditionOrTimeOut(() => callbackSucceeded);
+ AssertOnTimeout($"[OnListChanged][Client-{nonAuthority.LocalClientId}] client callback was not called");
+ }
+
+ yield return WaitForConditionOrTimeOut(OnVerifyData);
+ AssertOnTimeout("[NetworkList[index]] Failed to get/set items at index in list");
+
+ /*
+ * NetworkList.Set with same value (forced and non-forced updates)
+ */
+ var expectedUpdateCount = 0;
+ var actualUpdateCount = 0;
+
+ // Callback that verifies the changed event occurred and that the original and new values are correct
+ void TestForceUpdateCallback(NetworkListEvent _)
+ {
+ actualUpdateCount++;
+ }
+ nonAuthorityInstance.TheList.OnListChanged += TestForceUpdateCallback;
+
+ foreach (var testValue in Shuffle(m_ExpectedValues))
+ {
+ var testIndex = m_ExpectedValues.IndexOf(testValue);
+ var forceUpdate = testIndex % 2 == 0;
+ if (forceUpdate)
+ {
+ expectedUpdateCount++;
+ }
+ // Subscribe to the OnListChanged event on the client side and
+ authorityInstance.TheList.Set(testIndex, testValue, forceUpdate);
+ }
+
+ yield return WaitForConditionOrTimeOut(() => actualUpdateCount == expectedUpdateCount);
+ AssertOnTimeout($"[OnListChanged][Client-{nonAuthority.LocalClientId}] OnListChanged update was called an incorrect number of times");
+ nonAuthorityInstance.TheList.OnListChanged -= TestForceUpdateCallback;
+
+ /*
+ * NetworkList.Remove and NetworkList.RemoveAt
+ */
+ foreach (var testValue in Shuffle(m_ExpectedValues))
+ {
+ var testIndex = m_ExpectedValues.IndexOf(testValue);
+
+ // Add a new value to the end to ensure the list isn't depleted
+ var newValue = Random.Range(0, 99);
+ m_ExpectedValues.Add(newValue);
+ authorityInstance.TheList.Add(newValue);
+
+ var removeAt = testIndex % 2 == 0;
+ if (removeAt)
+ {
+ m_ExpectedValues.RemoveAt(testIndex);
+ authorityInstance.TheList.RemoveAt(testIndex);
+ }
+ else
+ {
+ m_ExpectedValues.Remove(testValue);
+ authorityInstance.TheList.Remove(testValue);
+ }
+ }
+ yield return WaitForConditionOrTimeOut(OnVerifyData);
+ AssertOnTimeout($"[Remove] List is incorrect after removing items");
+
+ /*
+ * NetworkList.Clear
+ */
+ m_ExpectedValues.Clear();
+ authorityInstance.TheList.Clear();
+
+ yield return WaitForConditionOrTimeOut(OnVerifyData);
+ AssertOnTimeout($"[Clear] List is incorrect after clearing items");
+ }
+
+ // don't extend this please
+ [UnityTest]
+ public IEnumerator LegacyPredicateTesting()
+ {
+ var authority = GetAuthorityNetworkManager();
+ var nonAuthority = GetNonAuthorityNetworkManager();
+
+ var instantiatedObject = SpawnObject(m_ListObjectPrefab, authority).GetComponent();
+
+ yield return WaitForSpawnedOnAllOrTimeOut(instantiatedObject);
+ AssertOnTimeout("Failed to spawn list object");
+
+ Assert.IsTrue(nonAuthority.SpawnManager.SpawnedObjects.TryGetValue(instantiatedObject.NetworkObjectId, out var nonAuthorityObject));
+
+ var authorityInstance = instantiatedObject.GetComponent();
+ var nonAuthorityInstance = nonAuthorityObject.GetComponent();
+
+ // WhenListContainsManyLargeValues_OverflowExceptionIsNotThrown
+ var overflowPredicate = new NetworkListTestPredicate(authorityInstance, nonAuthorityInstance, 20);
+ yield return WaitForConditionOrTimeOut(overflowPredicate);
+ AssertOnTimeout("Overflow exception shouldn't be thrown when adding many large values");
+
+ /*
+ * NetworkList Struct
+ */
+ bool VerifyList()
+ {
+ return nonAuthorityInstance.TheStructList.Count == authorityInstance.TheStructList.Count &&
+ nonAuthorityInstance.TheStructList[0].Value == authorityInstance.TheStructList[0].Value &&
+ nonAuthorityInstance.TheStructList[1].Value == authorityInstance.TheStructList[1].Value;
+ }
+
+ authorityInstance.TheStructList.Add(new StructUsedOnlyInNetworkList { Value = 1 });
+ authorityInstance.TheStructList.Add(new StructUsedOnlyInNetworkList { Value = 2 });
+ authorityInstance.TheStructList.SetDirty(true);
+
+ // Wait for the client-side to notify it is finished initializing and spawning.
+ yield return WaitForConditionOrTimeOut(VerifyList);
+ AssertOnTimeout("All list values should match between clients");
+ }
+
+ private int[] Shuffle(List list)
+ {
+ var rng = new System.Random();
+
+ // Order the list by a progression of random numbers
+ // This will do a shuffle of the list
+ return list.OrderBy(_ => rng.Next()).ToArray();
+ }
+ }
+
+ internal class NetworkListTest : NetworkBehaviour
+ {
+ public readonly NetworkList TheList = new();
+ public readonly NetworkList TheStructList = new();
+ public readonly NetworkList TheLargeList = new();
+
+ private void ListChanged(NetworkListEvent e)
+ {
+ ListDelegateTriggered = true;
+ }
+
+ public void Awake()
+ {
+ TheList.OnListChanged += ListChanged;
+ }
+
+ public override void OnDestroy()
+ {
+ TheList.OnListChanged -= ListChanged;
+ base.OnDestroy();
+ }
+
+ public bool ListDelegateTriggered;
+ }
+
+
+ ///
+ /// Handles the more generic conditional logic for NetworkList tests
+ /// which can be used with the NetcodeIntegrationTest.WaitForConditionOrTimeOut
+ /// that accepts anything derived from the class
+ /// as a parameter.
+ ///
+ internal class NetworkListTestPredicate : ConditionalPredicateBase
+ {
+ private readonly NetworkListTest m_AuthorityInstance;
+
+ private readonly NetworkListTest m_NonAuthorityInstance;
+
+ private string m_TestStageFailedMessage;
+
+ ///
+ /// Determines if the condition has been reached for the current NetworkListTestState
+ ///
+ protected override bool OnHasConditionBeenReached()
+ {
+ return OnContainsLarge() && OnVerifyData();
+ }
+
+ ///
+ /// Provides all information about the players for both sides for simplicity and informative sake.
+ ///
+ ///
+ private string ConditionFailedInfo()
+ {
+ return $"[ContainsLarge] condition test failed:\n Server List Count: {m_AuthorityInstance.TheList.Count} vs Client List Count: {m_NonAuthorityInstance.TheList.Count}\n" +
+ $"Server List Count: {m_AuthorityInstance.TheLargeList.Count} vs Client List Count: {m_NonAuthorityInstance.TheLargeList.Count}\n" +
+ $"Server Delegate Triggered: {m_AuthorityInstance.ListDelegateTriggered} | Client Delegate Triggered: {m_NonAuthorityInstance.ListDelegateTriggered}\n";
+ }
+
+ ///
+ /// When finished, check if a time out occurred and if so assert and provide meaningful information to troubleshoot why
+ ///
+ protected override void OnFinished()
+ {
+ Assert.IsFalse(TimedOut, $"{nameof(NetworkListTestPredicate)} timed out waiting for the ContainsLarge condition to be reached! \n" + ConditionFailedInfo());
+ }
+
+ // Uses the ArrayOperator and validates that on both sides the count and values are the same
+ private bool OnVerifyData()
+ {
+ // Wait until both sides have the same number of elements
+ if (m_AuthorityInstance.TheLargeList.Count != m_NonAuthorityInstance.TheLargeList.Count)
+ {
+ return false;
+ }
+
+ // Check the client values against the server values to make sure they match
+ for (int i = 0; i < m_AuthorityInstance.TheLargeList.Count; i++)
+ {
+ if (m_AuthorityInstance.TheLargeList[i] != m_NonAuthorityInstance.TheLargeList[i])
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ ///
+ /// The current version of this test only verified the count of the large list, so that is what this does
+ ///
+ private bool OnContainsLarge()
+ {
+ return m_AuthorityInstance.TheLargeList.Count == m_NonAuthorityInstance.TheLargeList.Count && OnVerifyData();
+ }
+
+ private const string k_CharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ private static string GenerateRandomString(int length)
+ {
+ var charArray = k_CharSet.Distinct().ToArray();
+ var result = new char[length];
+ for (int i = 0; i < length; i++)
+ {
+ result[i] = charArray[RandomNumberGenerator.GetInt32(charArray.Length)];
+ }
+
+ return new string(result);
+ }
+
+ public NetworkListTestPredicate(NetworkListTest authorityInstance, NetworkListTest nonAuthorityInstance, int elementCount)
+ {
+ m_AuthorityInstance = authorityInstance;
+ m_NonAuthorityInstance = nonAuthorityInstance;
+
+ for (var i = 0; i < elementCount; ++i)
+ {
+ m_AuthorityInstance.TheLargeList.Add(new FixedString128Bytes(GenerateRandomString(Random.Range(0, 99))));
+ }
+ }
+ }
+
+}
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkListTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkListTests.cs.meta
new file mode 100644
index 0000000000..2cdfab2387
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkListTests.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 9834036b76954617b9e09312903b2754
+timeCreated: 1758219317
\ No newline at end of file
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs
index 351b6ab9fd..06ef4969d0 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs
@@ -8,7 +8,6 @@
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
-using Random = UnityEngine.Random;
namespace Unity.Netcode.RuntimeTests
{
@@ -112,29 +111,15 @@ public enum SomeEnum
}
public readonly NetworkVariable TheScalar = new NetworkVariable();
public readonly NetworkVariable TheEnum = new NetworkVariable();
- public readonly NetworkList TheList = new NetworkList();
- public readonly NetworkList TheStructList = new NetworkList();
- public readonly NetworkList TheLargeList = new NetworkList();
public readonly NetworkVariable FixedString32 = new NetworkVariable();
- private void ListChanged(NetworkListEvent e)
- {
- ListDelegateTriggered = true;
- }
-
- public void Awake()
- {
- TheList.OnListChanged += ListChanged;
- }
-
public readonly NetworkVariable TheStruct = new NetworkVariable();
public readonly NetworkVariable TheClass = new NetworkVariable();
public NetworkVariable> TheTemplateStruct = new NetworkVariable>();
public NetworkVariable> TheTemplateClass = new NetworkVariable>();
- public bool ListDelegateTriggered;
public override void OnNetworkSpawn()
{
@@ -146,177 +131,6 @@ public override void OnNetworkSpawn()
}
}
- ///
- /// Handles the more generic conditional logic for NetworkList tests
- /// which can be used with the
- /// that accepts anything derived from the class
- /// as a parameter.
- ///
- internal class NetworkListTestPredicate : ConditionalPredicateBase
- {
- private const int k_MaxRandomValue = 1000;
-
- private Dictionary> m_StateFunctions;
-
- // Player1 component on the Server
- private NetworkVariableTest m_Player1OnServer;
-
- // Player1 component on client1
- private NetworkVariableTest m_Player1OnClient1;
-
- private string m_TestStageFailedMessage;
-
- public enum NetworkListTestStates
- {
- Add,
- ContainsLarge,
- Contains,
- VerifyData,
- IndexOf,
- }
-
- private NetworkListTestStates m_NetworkListTestState;
-
- public void SetNetworkListTestState(NetworkListTestStates networkListTestState)
- {
- m_NetworkListTestState = networkListTestState;
- }
-
- ///
- /// Determines if the condition has been reached for the current NetworkListTestState
- ///
- protected override bool OnHasConditionBeenReached()
- {
- var isStateRegistered = m_StateFunctions.ContainsKey(m_NetworkListTestState);
- Assert.IsTrue(isStateRegistered);
- return m_StateFunctions[m_NetworkListTestState].Invoke();
- }
-
- ///
- /// Provides all information about the players for both sides for simplicity and informative sake.
- ///
- ///
- private string ConditionFailedInfo()
- {
- return $"{m_NetworkListTestState} condition test failed:\n Server List Count: {m_Player1OnServer.TheList.Count} vs Client List Count: {m_Player1OnClient1.TheList.Count}\n" +
- $"Server List Count: {m_Player1OnServer.TheLargeList.Count} vs Client List Count: {m_Player1OnClient1.TheLargeList.Count}\n" +
- $"Server Delegate Triggered: {m_Player1OnServer.ListDelegateTriggered} | Client Delegate Triggered: {m_Player1OnClient1.ListDelegateTriggered}\n";
- }
-
- ///
- /// When finished, check if a time out occurred and if so assert and provide meaningful information to troubleshoot why
- ///
- protected override void OnFinished()
- {
- Assert.IsFalse(TimedOut, $"{nameof(NetworkListTestPredicate)} timed out waiting for the {m_NetworkListTestState} condition to be reached! \n" + ConditionFailedInfo());
- }
-
- // Uses the ArrayOperator and validates that on both sides the count and values are the same
- private bool OnVerifyData()
- {
- // Wait until both sides have the same number of elements
- if (m_Player1OnServer.TheList.Count != m_Player1OnClient1.TheList.Count)
- {
- return false;
- }
-
- // Check the client values against the server values to make sure they match
- for (int i = 0; i < m_Player1OnServer.TheList.Count; i++)
- {
- if (m_Player1OnServer.TheList[i] != m_Player1OnClient1.TheList[i])
- {
- return false;
- }
- }
- return true;
- }
-
- ///
- /// Verifies the data count, values, and that the ListDelegate on both sides was triggered
- ///
- private bool OnAdd()
- {
- bool wasTriggerred = m_Player1OnServer.ListDelegateTriggered && m_Player1OnClient1.ListDelegateTriggered;
- return wasTriggerred && OnVerifyData();
- }
-
- ///
- /// The current version of this test only verified the count of the large list, so that is what this does
- ///
- private bool OnContainsLarge()
- {
- return m_Player1OnServer.TheLargeList.Count == m_Player1OnClient1.TheLargeList.Count;
- }
-
- ///
- /// Tests NetworkList.Contains which also verifies all values are the same on both sides
- ///
- private bool OnContains()
- {
- // Wait until both sides have the same number of elements
- if (m_Player1OnServer.TheList.Count != m_Player1OnClient1.TheList.Count)
- {
- return false;
- }
-
- // Parse through all server values and use the NetworkList.Contains method to check if the value is in the list on the client side
- foreach (var serverValue in m_Player1OnServer.TheList)
- {
- if (!m_Player1OnClient1.TheList.Contains(serverValue))
- {
- return false;
- }
- }
- return true;
- }
-
- ///
- /// Tests NetworkList.IndexOf and verifies that all values are aligned on both sides
- ///
- private bool OnIndexOf()
- {
- foreach (var serverSideValue in m_Player1OnServer.TheList)
- {
- var indexToTest = m_Player1OnServer.TheList.IndexOf(serverSideValue);
- if (indexToTest != m_Player1OnServer.TheList.IndexOf(serverSideValue))
- {
- return false;
- }
- }
- return true;
- }
-
- public NetworkListTestPredicate(NetworkVariableTest player1OnServer, NetworkVariableTest player1OnClient1, NetworkListTestStates networkListTestState, int elementCount)
- {
- m_NetworkListTestState = networkListTestState;
- m_Player1OnServer = player1OnServer;
- m_Player1OnClient1 = player1OnClient1;
- m_StateFunctions = new Dictionary>
- {
- { NetworkListTestStates.Add, OnAdd },
- { NetworkListTestStates.ContainsLarge, OnContainsLarge },
- { NetworkListTestStates.Contains, OnContains },
- { NetworkListTestStates.VerifyData, OnVerifyData },
- { NetworkListTestStates.IndexOf, OnIndexOf }
- };
-
- if (networkListTestState == NetworkListTestStates.ContainsLarge)
- {
- for (var i = 0; i < elementCount; ++i)
- {
- m_Player1OnServer.TheLargeList.Add(new FixedString128Bytes());
- }
- }
- else
- {
- for (int i = 0; i < elementCount; i++)
- {
- m_Player1OnServer.TheList.Add(Random.Range(0, k_MaxRandomValue));
- }
- }
- }
- }
-
internal class NetvarDespawnShutdown : NetworkBehaviour
{
private NetworkVariable m_IntNetworkVariable = new NetworkVariable();
@@ -415,8 +229,6 @@ internal class NetworkVariableTests : NetcodeIntegrationTest
private const uint k_TestUInt = 0x12345678;
private const int k_TestVal1 = 111;
- private const int k_TestVal2 = 222;
- private const int k_TestVal3 = 333;
protected override bool m_EnableTimeTravel => true;
protected override bool m_SetupIsACoroutine => false;
@@ -434,8 +246,6 @@ public static void ClientNetworkVariableTestSpawned(NetworkVariableTest networkV
// Player1 component on client1
private NetworkVariableTest m_Player1OnClient1;
- private NetworkListTestPredicate m_NetworkListPredicateHandler;
-
private readonly bool m_EnsureLengthSafety;
public NetworkVariableTests(Serialization serialization)
@@ -520,17 +330,6 @@ private void InitializeServerAndClients(HostOrServer useHost)
m_Player1OnServer = authority;
m_Player1OnClient1 = nonAuthority;
- m_Player1OnServer.TheList.Clear();
-
- if (m_Player1OnServer.TheList.Count > 0)
- {
- throw new Exception("at least one server network container not empty at start");
- }
- if (m_Player1OnClient1.TheList.Count > 0)
- {
- throw new Exception("at least one client network container not empty at start");
- }
-
var instanceCount = useHost == HostOrServer.Server ? NumberOfClients * 2 : NumberOfClients * 3;
// Wait for the client-side to notify it is finished initializing and spawning.
success = WaitForConditionOrTimeOutWithTimeTravel(() => s_ClientNetworkVariableTestInstances.Count == instanceCount);
@@ -621,161 +420,6 @@ public void FixedString32Test([Values] HostOrServer useHost)
Assert.True(success, "Timed out waiting for client-side NetworkVariable to update!");
}
- [Test]
- public void NetworkListAdd([Values] HostOrServer useHost)
- {
- InitializeServerAndClients(useHost);
- m_NetworkListPredicateHandler = new NetworkListTestPredicate(m_Player1OnServer, m_Player1OnClient1, NetworkListTestPredicate.NetworkListTestStates.Add, 10);
- Assert.True(WaitForConditionOrTimeOutWithTimeTravel(m_NetworkListPredicateHandler));
- }
-
- [Test]
- public void WhenListContainsManyLargeValues_OverflowExceptionIsNotThrown([Values] HostOrServer useHost)
- {
- InitializeServerAndClients(useHost);
- m_NetworkListPredicateHandler = new NetworkListTestPredicate(m_Player1OnServer, m_Player1OnClient1, NetworkListTestPredicate.NetworkListTestStates.ContainsLarge, 20);
- Assert.True(WaitForConditionOrTimeOutWithTimeTravel(m_NetworkListPredicateHandler));
- }
-
- [Test]
- public void NetworkListContains([Values] HostOrServer useHost)
- {
- // Re-use the NetworkListAdd to initialize the server and client as well as make sure the list is populated
- NetworkListAdd(useHost);
-
- // Now test the NetworkList.Contains method
- m_NetworkListPredicateHandler.SetNetworkListTestState(NetworkListTestPredicate.NetworkListTestStates.Contains);
- Assert.True(WaitForConditionOrTimeOutWithTimeTravel(m_NetworkListPredicateHandler));
- }
-
-
- [Test]
- public void NetworkListInsert([Values] HostOrServer useHost)
- {
- // Re-use the NetworkListAdd to initialize the server and client as well as make sure the list is populated
- NetworkListAdd(useHost);
-
- // Now randomly insert a random value entry
- m_Player1OnServer.TheList.Insert(Random.Range(0, 9), Random.Range(1, 99));
-
- // Verify the element count and values on the client matches the server
- m_NetworkListPredicateHandler.SetNetworkListTestState(NetworkListTestPredicate.NetworkListTestStates.VerifyData);
- Assert.True(WaitForConditionOrTimeOutWithTimeTravel(m_NetworkListPredicateHandler));
- }
-
- [Test]
- public void NetworkListIndexOf([Values] HostOrServer useHost)
- {
- // Re-use the NetworkListAdd to initialize the server and client as well as make sure the list is populated
- NetworkListAdd(useHost);
-
- m_NetworkListPredicateHandler.SetNetworkListTestState(NetworkListTestPredicate.NetworkListTestStates.IndexOf);
- Assert.True(WaitForConditionOrTimeOutWithTimeTravel(m_NetworkListPredicateHandler));
- }
-
- [Test]
- public void NetworkListValueUpdate([Values] HostOrServer useHost)
- {
- var testSucceeded = false;
- InitializeServerAndClients(useHost);
- // Add 1 element value and verify it is the same on the client
- m_NetworkListPredicateHandler = new NetworkListTestPredicate(m_Player1OnServer, m_Player1OnClient1, NetworkListTestPredicate.NetworkListTestStates.Add, 1);
- Assert.True(WaitForConditionOrTimeOutWithTimeTravel(m_NetworkListPredicateHandler));
-
- // Setup our original and
- var previousValue = m_Player1OnServer.TheList[0];
- var updatedValue = previousValue + 10;
-
- // Callback that verifies the changed event occurred and that the original and new values are correct
- void TestValueUpdatedCallback(NetworkListEvent changedEvent)
- {
- testSucceeded = changedEvent.PreviousValue == previousValue &&
- changedEvent.Value == updatedValue;
- }
-
- // Subscribe to the OnListChanged event on the client side and
- m_Player1OnClient1.TheList.OnListChanged += TestValueUpdatedCallback;
- m_Player1OnServer.TheList[0] = updatedValue;
-
- // Wait until we know the client side matches the server side before checking if the callback was a success
- m_NetworkListPredicateHandler.SetNetworkListTestState(NetworkListTestPredicate.NetworkListTestStates.VerifyData);
- WaitForConditionOrTimeOutWithTimeTravel(m_NetworkListPredicateHandler);
-
- Assert.That(testSucceeded);
- m_Player1OnClient1.TheList.OnListChanged -= TestValueUpdatedCallback;
- }
-
- private List m_ExpectedValuesServer = new List();
- private List m_ExpectedValuesClient = new List();
-
- public enum ListRemoveTypes
- {
- Remove,
- RemoveAt
- }
-
-
- [Test]
- public void NetworkListRemoveTests([Values] HostOrServer useHost, [Values] ListRemoveTypes listRemoveType)
- {
- m_ExpectedValuesServer.Clear();
- m_ExpectedValuesClient.Clear();
- // Re-use the NetworkListAdd to initialize the server and client as well as make sure the list is populated
- NetworkListAdd(useHost);
-
- // Randomly remove a few entries
- m_Player1OnServer.TheList.OnListChanged += Server_OnListChanged;
- m_Player1OnClient1.TheList.OnListChanged += Client_OnListChanged;
-
- // Remove half of the elements
- for (int i = 0; i < (int)(m_Player1OnServer.TheList.Count * 0.5f); i++)
- {
- var index = Random.Range(0, m_Player1OnServer.TheList.Count - 1);
- var value = m_Player1OnServer.TheList[index];
- m_ExpectedValuesServer.Add(value);
- m_ExpectedValuesClient.Add(value);
-
- if (listRemoveType == ListRemoveTypes.RemoveAt)
- {
- m_Player1OnServer.TheList.RemoveAt(index);
- }
- else
- {
- m_Player1OnServer.TheList.Remove(value);
- }
- }
-
- // Verify the element count and values on the client matches the server
- m_NetworkListPredicateHandler.SetNetworkListTestState(NetworkListTestPredicate.NetworkListTestStates.VerifyData);
- Assert.True(WaitForConditionOrTimeOutWithTimeTravel(m_NetworkListPredicateHandler));
-
- Assert.True(m_ExpectedValuesServer.Count == 0, $"Server was not notified of all elements removed and still has {m_ExpectedValuesServer.Count} elements left!");
- Assert.True(m_ExpectedValuesClient.Count == 0, $"Client was not notified of all elements removed and still has {m_ExpectedValuesClient.Count} elements left!");
- }
-
- private void Server_OnListChanged(NetworkListEvent changeEvent)
- {
- Assert.True(m_ExpectedValuesServer.Contains(changeEvent.Value));
- m_ExpectedValuesServer.Remove(changeEvent.Value);
- }
-
- private void Client_OnListChanged(NetworkListEvent changeEvent)
- {
- Assert.True(m_ExpectedValuesClient.Contains(changeEvent.Value));
- m_ExpectedValuesClient.Remove(changeEvent.Value);
- }
-
- [Test]
- public void NetworkListClear([Values] HostOrServer useHost)
- {
- // Re-use the NetworkListAdd to initialize the server and client as well as make sure the list is populated
- NetworkListAdd(useHost);
- m_Player1OnServer.TheList.Clear();
- // Verify the element count and values on the client matches the server
- m_NetworkListPredicateHandler.SetNetworkListTestState(NetworkListTestPredicate.NetworkListTestStates.VerifyData);
- Assert.True(WaitForConditionOrTimeOutWithTimeTravel(m_NetworkListPredicateHandler));
- }
-
[Test]
public void TestNetworkVariableClass([Values] HostOrServer useHost)
{
@@ -813,26 +457,6 @@ bool VerifyClass()
Assert.True(WaitForConditionOrTimeOutWithTimeTravel(VerifyClass));
}
- [Test]
- public void TestNetworkListStruct([Values] HostOrServer useHost)
- {
- InitializeServerAndClients(useHost);
-
- bool VerifyList()
- {
- return m_Player1OnClient1.TheStructList.Count == m_Player1OnServer.TheStructList.Count &&
- m_Player1OnClient1.TheStructList[0].Value == m_Player1OnServer.TheStructList[0].Value &&
- m_Player1OnClient1.TheStructList[1].Value == m_Player1OnServer.TheStructList[1].Value;
- }
-
- m_Player1OnServer.TheStructList.Add(new StructUsedOnlyInNetworkList { Value = 1 });
- m_Player1OnServer.TheStructList.Add(new StructUsedOnlyInNetworkList { Value = 2 });
- m_Player1OnServer.TheStructList.SetDirty(true);
-
- // Wait for the client-side to notify it is finished initializing and spawning.
- Assert.True(WaitForConditionOrTimeOutWithTimeTravel(VerifyList));
- }
-
[Test]
public void TestNetworkVariableStruct([Values] HostOrServer useHost)
{
@@ -5256,7 +4880,6 @@ protected override IEnumerator OnTearDown()
{
Time.timeScale = m_OriginalTimeScale;
- m_NetworkListPredicateHandler = null;
yield return base.OnTearDown();
}
}
diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json
index 2d5ff27d22..bafc5b9943 100644
--- a/com.unity.netcode.gameobjects/package.json
+++ b/com.unity.netcode.gameobjects/package.json
@@ -2,7 +2,7 @@
"name": "com.unity.netcode.gameobjects",
"displayName": "Netcode for GameObjects",
"description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.",
- "version": "2.5.2",
+ "version": "2.6.0",
"unity": "6000.0",
"dependencies": {
"com.unity.nuget.mono-cecil": "1.11.4",
@@ -15,4 +15,4 @@
"path": "Samples~/Bootstrap"
}
]
-}
\ No newline at end of file
+}