using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine.TestTools; using Random = UnityEngine.Random; namespace Unity.Netcode.RuntimeTests { /// /// Validates using managed collections with NetworkVariable. /// Managed Collections Tested: /// - List /// - Dictionary /// - HashSet /// This also does some testing on nested collections, but does /// not test every possible combination. /// [TestFixture(HostOrServer.Host, CollectionTypes.List)] [TestFixture(HostOrServer.Server, CollectionTypes.List)] public class NetworkVariableCollectionsTests : NetcodeIntegrationTest { public enum CollectionTypes { Dictionary, List, } protected override int NumberOfClients => 2; private CollectionTypes m_CollectionType; public NetworkVariableCollectionsTests(HostOrServer hostOrServer, CollectionTypes collectionType) : base(hostOrServer) { m_CollectionType = collectionType; } protected override IEnumerator OnSetup() { ListTestHelperInt.ResetState(); ListTestHelperListInt.ResetState(); ListTestHelperSerializableObject.ResetState(); ListTestHelperListSerializableObject.ResetState(); DictionaryTestHelper.ResetState(); NestedDictionaryTestHelper.ResetState(); HashSetBaseTypeTestHelper.ResetState(); return base.OnSetup(); } protected override void OnCreatePlayerPrefab() { m_PlayerPrefab.AddComponent(); m_PlayerPrefab.AddComponent(); m_PlayerPrefab.AddComponent(); m_PlayerPrefab.AddComponent(); m_PlayerPrefab.AddComponent(); m_PlayerPrefab.AddComponent(); m_PlayerPrefab.AddComponent(); base.OnCreatePlayerPrefab(); } private List GetRandomIntList(int count) { var list = new List(); for (int i = 0; i < count; i++) { list.Add(Random.Range(int.MinValue, int.MaxValue)); } return list; } [UnityTest] public IEnumerator TestListBuiltInTypeCollections() { var compInt = (ListTestHelperInt)null; var compListInt = (ListTestHelperListInt)null; var compIntServer = (ListTestHelperInt)null; var compListIntServer = (ListTestHelperListInt)null; var clientList = m_ClientNetworkManagers.ToList(); if (m_ServerNetworkManager.IsHost) { clientList.Insert(0, m_ServerNetworkManager); } foreach (var client in clientList) { /////////////////////////////////////////////////////////////////////////// // List Single dimension list compInt = client.LocalClient.PlayerObject.GetComponent(); compIntServer = m_PlayerNetworkObjects[NetworkManager.ServerClientId][client.LocalClientId].GetComponent(); yield return WaitForConditionOrTimeOut(() => compInt.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compInt.OwnerClientId}'s {nameof(ListTestHelperInt)} {compInt.name} component match!"); yield return WaitForConditionOrTimeOut(() => compIntServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compIntServer.OwnerClientId}'s {nameof(ListTestHelperInt)} {compIntServer.name} component match!"); var randomInt = Random.Range(int.MinValue, int.MaxValue); ////////////////////////////////// // Owner Add int compInt.Add(randomInt, ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compInt.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} add failed to synchronize on {nameof(ListTestHelperInt)} {compInt.name}!"); ////////////////////////////////// // Server Add int compIntServer.Add(randomInt, ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compIntServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server add failed to synchronize on {nameof(ListTestHelperInt)} {compIntServer.name}!"); ////////////////////////////////// // Owner Remove int var index = Random.Range(0, compInt.ListCollectionOwner.Value.Count - 1); var valueIntRemove = compInt.ListCollectionOwner.Value[index]; compInt.Remove(valueIntRemove, ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compInt.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} remove failed to synchronize on {nameof(ListTestHelperInt)} {compInt.name}!"); ////////////////////////////////// // Server Remove int compIntServer.Remove(valueIntRemove, ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compIntServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server remove failed to synchronize on {nameof(ListTestHelperInt)} {compIntServer.name}!"); yield return WaitForConditionOrTimeOut(() => compInt.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compInt.OwnerClientId}'s {nameof(ListTestHelperInt)} {compInt.name} component match!"); yield return WaitForConditionOrTimeOut(() => compIntServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compIntServer.OwnerClientId}'s {nameof(ListTestHelperInt)} {compIntServer.name} component match!"); //////////////////////////////////// // Owner Change int var valueIntChange = Random.Range(int.MinValue, int.MaxValue); compInt.ListCollectionOwner.Value[index] = valueIntChange; compInt.ListCollectionOwner.CheckDirtyState(); yield return WaitForConditionOrTimeOut(() => compInt.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} change failed to synchronize on {nameof(ListTestHelperInt)} {compInt.name}!"); ////////////////////////////////// // Server Change int compIntServer.ListCollectionServer.Value[index] = valueIntChange; compIntServer.ListCollectionServer.CheckDirtyState(); yield return WaitForConditionOrTimeOut(() => compIntServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server change failed to synchronize on {nameof(ListTestHelperInt)} {compIntServer.name}!"); //////////////////////////////////// // Owner Add Range compInt.AddRange(GetRandomIntList(5), ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compInt.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} add range failed to synchronize on {nameof(ListTestHelperInt)} {compInt.name}!"); ////////////////////////////////// // Server Add Range compIntServer.AddRange(GetRandomIntList(5), ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compIntServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server add range failed to synchronize on {nameof(ListTestHelperInt)} {compIntServer.name}!"); yield return WaitForConditionOrTimeOut(() => compInt.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compInt.OwnerClientId}'s {nameof(ListTestHelperInt)} {compInt.name} component match!"); yield return WaitForConditionOrTimeOut(() => compIntServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compIntServer.OwnerClientId}'s {nameof(ListTestHelperInt)} {compIntServer.name} component match!"); //////////////////////////////////// // Owner Full Set compInt.FullSet(GetRandomIntList(5), ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compInt.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} full set failed to synchronize on {nameof(ListTestHelperInt)} {compInt.name}!"); ////////////////////////////////// // Server Full Set compIntServer.FullSet(GetRandomIntList(5), ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compIntServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server full set failed to synchronize on {nameof(ListTestHelperInt)} {compIntServer.name}!"); //////////////////////////////////// // Owner Clear compInt.Clear(ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compInt.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} clear failed to synchronize on {nameof(ListTestHelperInt)} {compInt.name}!"); ////////////////////////////////// // Server Clear compIntServer.Clear(ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compIntServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server clear failed to synchronize on {nameof(ListTestHelperInt)} {compIntServer.name}!"); yield return WaitForConditionOrTimeOut(() => compInt.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compInt.OwnerClientId}'s {nameof(ListTestHelperInt)} {compInt.name} component match!"); yield return WaitForConditionOrTimeOut(() => compIntServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compIntServer.OwnerClientId}'s {nameof(ListTestHelperInt)} {compIntServer.name} component match!"); /////////////////////////////////////////////////////////////////////////// // List> Nested List Validation compListInt = client.LocalClient.PlayerObject.GetComponent(); compListIntServer = m_PlayerNetworkObjects[NetworkManager.ServerClientId][client.LocalClientId].GetComponent(); yield return WaitForConditionOrTimeOut(() => compListInt.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compListInt.OwnerClientId}'s {nameof(ListTestHelperInt)} {compListInt.name} component match! {compListInt.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compListIntServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compListIntServer.OwnerClientId}'s {nameof(ListTestHelperInt)} {compListIntServer.name} component match! {compListIntServer.GetLog()}"); ////////////////////////////////// // Owner Add List item compListInt.Add(GetRandomIntList(5), ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compListInt.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} add failed to synchronize on {nameof(ListTestHelperListInt)} {compListInt.name}! {compListInt.GetLog()}"); ////////////////////////////////// // Server Add List item compListIntServer.Add(GetRandomIntList(5), ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compListIntServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server add failed to synchronize on {nameof(ListTestHelperListInt)} {compListIntServer.name}! {compListIntServer.GetLog()}"); ////////////////////////////////// // Owner Remove List item index = Random.Range(0, compListInt.ListCollectionOwner.Value.Count - 1); compListInt.Remove(compListInt.ListCollectionOwner.Value[index], ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compInt.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} remove failed to synchronize on {nameof(ListTestHelperListInt)} {compListInt.name}! {compListInt.GetLog()}"); ////////////////////////////////// // Server Remove List item index = Random.Range(0, compListIntServer.ListCollectionServer.Value.Count - 1); compListIntServer.Remove(compListIntServer.ListCollectionServer.Value[index], ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compListIntServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server remove failed to synchronize on {nameof(ListTestHelperListInt)} {compListIntServer.name}! {compListIntServer.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compListInt.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compListInt.OwnerClientId}'s {nameof(ListTestHelperInt)} {compListInt.name} component match! {compListInt.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compListIntServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compListIntServer.OwnerClientId}'s {nameof(ListTestHelperInt)} {compListIntServer.name} component match! {compListIntServer.GetLog()}"); //////////////////////////////////// // Owner Change List item index = Random.Range(0, compListInt.ListCollectionOwner.Value.Count - 1); compListInt.ListCollectionOwner.Value[index] = GetRandomIntList(5); compListInt.ListCollectionOwner.CheckDirtyState(); Assert.True(compListInt.ListCollectionOwner.IsDirty(), "Client Should be dirty!"); yield return WaitForConditionOrTimeOut(() => compListInt.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} change index ({index}) failed to synchronize on {nameof(ListTestHelperListInt)} {compListInt.name}! {compListInt.GetLog()}"); ////////////////////////////////// // Server Change List item index = Random.Range(0, compListIntServer.ListCollectionServer.Value.Count - 1); compListIntServer.ListCollectionServer.Value[index] = GetRandomIntList(5); compListIntServer.ListCollectionServer.CheckDirtyState(); Assert.True(compListIntServer.ListCollectionServer.IsDirty(), "Server Should be dirty!"); yield return WaitForConditionOrTimeOut(() => compListIntServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server change failed to synchronize on {nameof(ListTestHelperListInt)} {compListIntServer.name}! {compListIntServer.GetLog()}"); //////////////////////////////////// // Owner Add Range of List items var randomintListOfList = new List>(); for (int i = 0; i < 5; i++) { randomintListOfList.Add(GetRandomIntList(5)); } compListInt.AddRange(randomintListOfList, ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compListInt.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} add range failed to synchronize on {nameof(ListTestHelperListInt)} {compListInt.name}! {compListInt.GetLog()}"); ////////////////////////////////// // Server Add Range of List items randomintListOfList = new List>(); for (int i = 0; i < 5; i++) { randomintListOfList.Add(GetRandomIntList(5)); } compListIntServer.AddRange(randomintListOfList, ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compListIntServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server add range failed to synchronize on {nameof(ListTestHelperListInt)} {compListIntServer.name}! {compListIntServer.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compListInt.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compListInt.OwnerClientId}'s {nameof(ListTestHelperInt)} {compListInt.name} component match!"); yield return WaitForConditionOrTimeOut(() => compListIntServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compListIntServer.OwnerClientId}'s {nameof(ListTestHelperInt)} {compListIntServer.name} component match!"); //////////////////////////////////// // Owner Full Set List> randomintListOfList = new List>(); for (int i = 0; i < 5; i++) { randomintListOfList.Add(GetRandomIntList(5)); } compListInt.FullSet(randomintListOfList, ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compListInt.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} full set failed to synchronize on {nameof(ListTestHelperListInt)} {compListInt.name}! {compListInt.GetLog()}"); ////////////////////////////////// // Server Full Set List> randomintListOfList = new List>(); for (int i = 0; i < 5; i++) { randomintListOfList.Add(GetRandomIntList(5)); } compListIntServer.FullSet(randomintListOfList, ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compListIntServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server full set failed to synchronize on {nameof(ListTestHelperListInt)} {compListIntServer.name}! {compListIntServer.GetLog()}"); //////////////////////////////////// // Owner Clear List> compListInt.Clear(ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compListInt.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} clear failed to synchronize on {nameof(ListTestHelperListInt)} {compListInt.name}! {compListInt.GetLog()}"); ////////////////////////////////// // Server Clear List> compListIntServer.Clear(ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compListIntServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server clear failed to synchronize on {nameof(ListTestHelperListInt)} {compListIntServer.name}! {compListIntServer.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compListInt.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compListInt.OwnerClientId}'s {nameof(ListTestHelperInt)} {compListInt.name} component match!"); yield return WaitForConditionOrTimeOut(() => compListIntServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compListIntServer.OwnerClientId}'s {nameof(ListTestHelperInt)} {compListIntServer.name} component match!"); } } [UnityTest] public IEnumerator TestListSerializableObjectCollections() { var compObject = (ListTestHelperSerializableObject)null; var compObjectServer = (ListTestHelperSerializableObject)null; var compListObject = (ListTestHelperListSerializableObject)null; var compListObjectServer = (ListTestHelperListSerializableObject)null; var clientList = m_ClientNetworkManagers.ToList(); if (m_ServerNetworkManager.IsHost) { clientList.Insert(0, m_ServerNetworkManager); } foreach (var client in clientList) { /////////////////////////////////////////////////////////////////////////// // List Single dimension list compObject = client.LocalClient.PlayerObject.GetComponent(); compObjectServer = m_PlayerNetworkObjects[NetworkManager.ServerClientId][client.LocalClientId].GetComponent(); yield return WaitForConditionOrTimeOut(() => compObject.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compObject.OwnerClientId}'s {nameof(ListTestHelperSerializableObject)} {compObject.name} component match!"); yield return WaitForConditionOrTimeOut(() => compObjectServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compObjectServer.OwnerClientId}'s {nameof(ListTestHelperSerializableObject)} {compObjectServer.name} component match!"); ////////////////////////////////// // Owner Add SerializableObject compObject.Add(SerializableObject.GetRandomObject(), ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compObject.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} add failed to synchronize on {nameof(ListTestHelperSerializableObject)} {compObject.name}!"); ////////////////////////////////// // Server Add SerializableObject compObjectServer.Add(SerializableObject.GetRandomObject(), ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server add failed to synchronize on {nameof(ListTestHelperSerializableObject)} {compObjectServer.name}!"); ////////////////////////////////// // Owner Remove SerializableObject var index = Random.Range(0, compObject.ListCollectionOwner.Value.Count - 1); var valueIntRemove = compObject.ListCollectionOwner.Value[index]; compObject.Remove(valueIntRemove, ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compObject.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} remove failed to synchronize on {nameof(ListTestHelperSerializableObject)} {compObject.name}!"); ////////////////////////////////// // Server Remove SerializableObject compObjectServer.Remove(valueIntRemove, ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server remove failed to synchronize on {nameof(ListTestHelperSerializableObject)} {compObjectServer.name}!"); yield return WaitForConditionOrTimeOut(() => compObject.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compObject.OwnerClientId}'s {nameof(ListTestHelperSerializableObject)} {compObject.name} component match!"); yield return WaitForConditionOrTimeOut(() => compObjectServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compObjectServer.OwnerClientId}'s {nameof(ListTestHelperSerializableObject)} {compObjectServer.name} component match!"); //////////////////////////////////// // Owner Change SerializableObject compObject.ListCollectionOwner.Value[index] = SerializableObject.GetRandomObject(); compObject.ListCollectionOwner.CheckDirtyState(); yield return WaitForConditionOrTimeOut(() => compObject.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} change failed to synchronize on {nameof(ListTestHelperSerializableObject)} {compObject.name}!"); ////////////////////////////////// // Server Change SerializableObject compObjectServer.ListCollectionServer.Value[index] = SerializableObject.GetRandomObject(); compObjectServer.ListCollectionServer.CheckDirtyState(); yield return WaitForConditionOrTimeOut(() => compObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server change failed to synchronize on {nameof(ListTestHelperSerializableObject)} {compObjectServer.name}!"); //////////////////////////////////// // Owner Add Range SerializableObjects compObject.AddRange(SerializableObject.GetListOfRandomObjects(5), ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compObject.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} add range failed to synchronize on {nameof(ListTestHelperSerializableObject)} {compObject.name}!"); ////////////////////////////////// // Server Add Range SerializableObjects compObjectServer.AddRange(SerializableObject.GetListOfRandomObjects(5), ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server add range failed to synchronize on {nameof(ListTestHelperSerializableObject)} {compObjectServer.name}!"); yield return WaitForConditionOrTimeOut(() => compObject.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compObject.OwnerClientId}'s {nameof(ListTestHelperSerializableObject)} {compObject.name} component match!"); yield return WaitForConditionOrTimeOut(() => compObjectServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compObjectServer.OwnerClientId}'s {nameof(ListTestHelperSerializableObject)} {compObjectServer.name} component match!"); //////////////////////////////////// // Owner Full Set SerializableObjects compObject.FullSet(SerializableObject.GetListOfRandomObjects(5), ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compObject.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} full set failed to synchronize on {nameof(ListTestHelperSerializableObject)} {compObject.name}!"); ////////////////////////////////// // Server Full Set SerializableObjects compObjectServer.FullSet(SerializableObject.GetListOfRandomObjects(5), ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server full set failed to synchronize on {nameof(ListTestHelperSerializableObject)} {compObjectServer.name}!"); //////////////////////////////////// // Owner Clear compObject.Clear(ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compObject.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} clear failed to synchronize on {nameof(ListTestHelperSerializableObject)} {compObject.name}!"); ////////////////////////////////// // Server Clear compObjectServer.Clear(ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server clear failed to synchronize on {nameof(ListTestHelperSerializableObject)} {compObjectServer.name}!"); yield return WaitForConditionOrTimeOut(() => compObject.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compObject.OwnerClientId}'s {nameof(ListTestHelperSerializableObject)} {compObject.name} component match!"); yield return WaitForConditionOrTimeOut(() => compObjectServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compObjectServer.OwnerClientId}'s {nameof(ListTestHelperSerializableObject)} {compObjectServer.name} component match!"); /////////////////////////////////////////////////////////////////////////// // List> Nested List Validation compListObject = client.LocalClient.PlayerObject.GetComponent(); compListObjectServer = m_PlayerNetworkObjects[NetworkManager.ServerClientId][client.LocalClientId].GetComponent(); yield return WaitForConditionOrTimeOut(() => compListObject.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compListObject.OwnerClientId}'s {nameof(ListTestHelperListSerializableObject)} {compListObject.name} component match! {compListObject.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compListObjectServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compListObjectServer.OwnerClientId}'s {nameof(ListTestHelperListSerializableObject)} {compListObjectServer.name} component match! {compListObjectServer.GetLog()}"); ////////////////////////////////// // Owner Add List item compListObject.Add(SerializableObject.GetListOfRandomObjects(5), ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compListObject.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} add failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObject.name}! {compListObject.GetLog()}"); ////////////////////////////////// // Server Add List item compListObjectServer.Add(SerializableObject.GetListOfRandomObjects(5), ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compListObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server add failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObjectServer.name}! {compListObjectServer.GetLog()}"); ////////////////////////////////// // Owner Remove List item index = Random.Range(0, compListObject.ListCollectionOwner.Value.Count - 1); compListObject.Remove(compListObject.ListCollectionOwner.Value[index], ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compListObject.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} remove failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObject.name}! {compListObject.GetLog()}"); ////////////////////////////////// // Server Remove List item index = Random.Range(0, compListObjectServer.ListCollectionServer.Value.Count - 1); compListObjectServer.Remove(compListObjectServer.ListCollectionServer.Value[index], ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compListObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server remove failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObjectServer.name}! {compListObjectServer.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compListObject.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compListObject.OwnerClientId}'s {nameof(ListTestHelperListSerializableObject)} {compListObject.name} component match! {compListObject.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compListObjectServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compListObjectServer.OwnerClientId}'s {nameof(ListTestHelperListSerializableObject)} {compListObjectServer.name} component match! {compListObjectServer.GetLog()}"); //////////////////////////////////// // Owner Change List item index = Random.Range(0, compListObject.ListCollectionOwner.Value.Count - 1); compListObject.ListCollectionOwner.Value[index] = SerializableObject.GetListOfRandomObjects(5); compListObject.ListCollectionOwner.CheckDirtyState(); Assert.True(compListObject.ListCollectionOwner.IsDirty(), "Client Should be dirty!"); yield return WaitForConditionOrTimeOut(() => compListObject.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} change index ({index}) failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObject.name}! {compListObject.GetLog()}"); ////////////////////////////////// // Server Change List item index = Random.Range(0, compListObjectServer.ListCollectionServer.Value.Count - 1); compListObjectServer.ListCollectionServer.Value[index] = SerializableObject.GetListOfRandomObjects(5); compListObjectServer.ListCollectionServer.CheckDirtyState(); Assert.True(compListObjectServer.ListCollectionServer.IsDirty(), "Server Should be dirty!"); yield return WaitForConditionOrTimeOut(() => compListObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server change failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObjectServer.name}! {compListObjectServer.GetLog()}"); //////////////////////////////////// // Owner Add Range of List items compListObject.AddRange(SerializableObject.GetListOfListOfRandomObjects(5, 5), ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compListObject.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} add range failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObject.name}! {compListObject.GetLog()}"); ////////////////////////////////// // Server Add Range of List items compListObjectServer.AddRange(SerializableObject.GetListOfListOfRandomObjects(5, 5), ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compListObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server add range failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObjectServer.name}! {compListObjectServer.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compListObject.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compListObject.OwnerClientId}'s {nameof(ListTestHelperListSerializableObject)} {compListObject.name} component match!"); yield return WaitForConditionOrTimeOut(() => compListObjectServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compListObjectServer.OwnerClientId}'s {nameof(ListTestHelperListSerializableObject)} {compListObjectServer.name} component match!"); //////////////////////////////////// // Owner Full Set List> compListObject.FullSet(SerializableObject.GetListOfListOfRandomObjects(5, 5), ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compListObject.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} full set failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObject.name}!"); ////////////////////////////////// // Server Full Set List> compListObjectServer.FullSet(SerializableObject.GetListOfListOfRandomObjects(5, 5), ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compListObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server full set failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObjectServer.name}!"); //////////////////////////////////// // Owner Clear List> compListObject.Clear(ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compListObject.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} clear failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObject.name}!"); ////////////////////////////////// // Server Clear List> compListObjectServer.Clear(ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compListObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server clear failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObjectServer.name}!"); yield return WaitForConditionOrTimeOut(() => compListObject.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compListObject.OwnerClientId}'s {nameof(ListTestHelperListSerializableObject)} {compListObject.name} component match!"); yield return WaitForConditionOrTimeOut(() => compListObjectServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compListObjectServer.OwnerClientId}'s {nameof(ListTestHelperListSerializableObject)} {compListObjectServer.name} component match!"); } } private int m_CurrentKey; private int GetNextKey() { m_CurrentKey++; return m_CurrentKey; } [UnityTest] public IEnumerator TestDictionaryCollections() { var compDictionary = (DictionaryTestHelper)null; var compDictionaryServer = (DictionaryTestHelper)null; var className = $"{nameof(DictionaryTestHelper)}"; var clientList = m_ClientNetworkManagers.ToList(); if (m_ServerNetworkManager.IsHost) { clientList.Insert(0, m_ServerNetworkManager); } m_CurrentKey = 1000; foreach (var client in clientList) { /////////////////////////////////////////////////////////////////////////// // Dictionary> nested dictionaries compDictionary = client.LocalClient.PlayerObject.GetComponent(); compDictionaryServer = m_PlayerNetworkObjects[NetworkManager.ServerClientId][client.LocalClientId].GetComponent(); yield return WaitForConditionOrTimeOut(() => compDictionary.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compDictionary.OwnerClientId}'s {className} {compDictionary.name} component match! {compDictionary.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compDictionaryServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compDictionaryServer.OwnerClientId}'s {className} {compDictionaryServer.name} component match! {compDictionaryServer.GetLog()}"); ////////////////////////////////// // Owner Add SerializableObject Entry compDictionary.Add((GetNextKey(), SerializableObject.GetRandomObject()), ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} add failed to synchronize on {className} {compDictionary.name}! {compDictionary.GetLog()}"); ////////////////////////////////// // Server Add SerializableObject Entry compDictionaryServer.Add((GetNextKey(), SerializableObject.GetRandomObject()), ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server add failed to synchronize on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}"); ////////////////////////////////// // Owner Remove SerializableObject Entry var index = Random.Range(0, compDictionary.ListCollectionOwner.Value.Keys.Count - 1); var valueInt = compDictionary.ListCollectionOwner.Value.Keys.ToList()[index]; compDictionary.Remove(valueInt, ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} remove failed to synchronize on {className} {compDictionary.name}! {compDictionary.GetLog()}"); ////////////////////////////////// // Server Remove SerializableObject Entry index = Random.Range(0, compDictionary.ListCollectionOwner.Value.Keys.Count - 1); valueInt = compDictionary.ListCollectionOwner.Value.Keys.ToList()[index]; compDictionaryServer.Remove(valueInt, ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server remove failed to synchronize on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compDictionary.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compDictionary.OwnerClientId}'s {className} {compDictionary.name} component match! {compDictionary.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compDictionaryServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compDictionaryServer.OwnerClientId}'s {className} {compDictionaryServer.name} component match! {compDictionaryServer.GetLog()}"); //////////////////////////////////// // Owner Change SerializableObject Entry index = Random.Range(0, compDictionary.ListCollectionOwner.Value.Keys.Count - 1); valueInt = compDictionary.ListCollectionOwner.Value.Keys.ToList()[index]; compDictionary.ListCollectionOwner.Value[valueInt] = SerializableObject.GetRandomObject(); compDictionary.ListCollectionOwner.CheckDirtyState(); yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} change failed to synchronize on {className} {compDictionary.name}! {compDictionary.GetLog()}"); ////////////////////////////////// // Server Change SerializableObject index = Random.Range(0, compDictionaryServer.ListCollectionOwner.Value.Keys.Count - 1); valueInt = compDictionaryServer.ListCollectionOwner.Value.Keys.ToList()[index]; compDictionaryServer.ListCollectionServer.Value[valueInt] = SerializableObject.GetRandomObject(); compDictionaryServer.ListCollectionServer.CheckDirtyState(); yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server change failed to synchronize on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}"); //////////////////////////////////// // Owner Full Set Dictionary compDictionary.FullSet(DictionaryTestHelper.GetDictionaryValues(), ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} full set failed to synchronize on {className} {compDictionary.name}! {compDictionary.GetLog()}"); ////////////////////////////////// // Server Full Set Dictionary compDictionaryServer.FullSet(DictionaryTestHelper.GetDictionaryValues(), ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server full set failed to synchronize on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}"); //////////////////////////////////// // Owner Clear compDictionary.Clear(ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} clear failed to synchronize on {className} {compDictionary.name}! {compDictionary.GetLog()}"); ////////////////////////////////// // Server Clear compDictionaryServer.Clear(ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server clear failed to synchronize on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compDictionary.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compDictionary.OwnerClientId}'s {className} {compDictionary.name} component match! {compDictionary.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compDictionaryServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compDictionaryServer.OwnerClientId}'s {className} {compDictionaryServer.name} component match! {compDictionaryServer.GetLog()}"); } } [UnityTest] public IEnumerator TestDictionaryNestedCollections() { var compDictionary = (NestedDictionaryTestHelper)null; var compDictionaryServer = (NestedDictionaryTestHelper)null; var className = $"{nameof(NestedDictionaryTestHelper)}"; var clientList = m_ClientNetworkManagers.ToList(); if (m_ServerNetworkManager.IsHost) { clientList.Insert(0, m_ServerNetworkManager); } m_CurrentKey = 1000; foreach (var client in clientList) { /////////////////////////////////////////////////////////////////////////// // Dictionary> nested dictionaries compDictionary = client.LocalClient.PlayerObject.GetComponent(); compDictionaryServer = m_PlayerNetworkObjects[NetworkManager.ServerClientId][client.LocalClientId].GetComponent(); yield return WaitForConditionOrTimeOut(() => compDictionary.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compDictionary.OwnerClientId}'s {className} {compDictionary.name} component match! {compDictionary.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compDictionaryServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compDictionaryServer.OwnerClientId}'s {className} {compDictionaryServer.name} component match! {compDictionaryServer.GetLog()}"); ////////////////////////////////// // Owner Add Dictionary compDictionary.Add((GetNextKey(), NestedDictionaryTestHelper.GetDictionaryValues()), ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} add failed to synchronize on {className} {compDictionary.name}! {compDictionary.GetLog()}"); ////////////////////////////////// // Server Add Dictionary compDictionaryServer.Add((GetNextKey(), NestedDictionaryTestHelper.GetDictionaryValues()), ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server add failed to synchronize on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}"); ////////////////////////////////// // Owner Remove Dictionary var index = Random.Range(0, compDictionary.ListCollectionOwner.Value.Keys.Count - 1); var valueInt = compDictionary.ListCollectionOwner.Value.Keys.ToList()[index]; compDictionary.Remove(valueInt, ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} remove failed to synchronize on {className} {compDictionary.name}! {compDictionary.GetLog()}"); ////////////////////////////////// // Server Remove Dictionary index = Random.Range(0, compDictionaryServer.ListCollectionOwner.Value.Keys.Count - 1); valueInt = compDictionaryServer.ListCollectionOwner.Value.Keys.ToList()[index]; compDictionaryServer.Remove(valueInt, ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server remove failed to synchronize on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compDictionary.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compDictionary.OwnerClientId}'s {className} {compDictionary.name} component match! {compDictionary.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compDictionaryServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compDictionaryServer.OwnerClientId}'s {className} {compDictionaryServer.name} component match! {compDictionaryServer.GetLog()}"); //////////////////////////////////// // Owner Change Dictionary index = Random.Range(0, compDictionary.ListCollectionOwner.Value.Keys.Count - 1); valueInt = compDictionary.ListCollectionOwner.Value.Keys.ToList()[index]; compDictionary.ListCollectionOwner.Value[valueInt] = NestedDictionaryTestHelper.GetDictionaryValues(); compDictionary.ListCollectionOwner.CheckDirtyState(); yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} change failed to synchronize on {className} {compDictionary.name}! {compDictionary.GetLog()}"); ////////////////////////////////// // Server Change Dictionary index = Random.Range(0, compDictionaryServer.ListCollectionOwner.Value.Keys.Count - 1); valueInt = compDictionaryServer.ListCollectionOwner.Value.Keys.ToList()[index]; compDictionaryServer.ListCollectionServer.Value[index] = NestedDictionaryTestHelper.GetDictionaryValues(); compDictionaryServer.ListCollectionServer.CheckDirtyState(); yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server change failed to synchronize on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}"); //////////////////////////////////// // Owner Full Set Nested Dictionaries compDictionary.FullSet(NestedDictionaryTestHelper.GetNestedDictionaryValues(), ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} full set failed to synchronize on {className} {compDictionary.name}! {compDictionary.GetLog()}"); ////////////////////////////////// // Server Full Set Nested Dictionaries compDictionaryServer.FullSet(NestedDictionaryTestHelper.GetNestedDictionaryValues(), ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server full set failed to synchronize on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}"); //////////////////////////////////// // Owner Clear compDictionary.Clear(ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} clear failed to synchronize on {className} {compDictionary.name}! {compDictionary.GetLog()}"); ////////////////////////////////// // Server Clear compDictionaryServer.Clear(ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server clear failed to synchronize on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compDictionary.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compDictionary.OwnerClientId}'s {className} {compDictionary.name} component match! {compDictionary.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compDictionaryServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compDictionaryServer.OwnerClientId}'s {className} {compDictionaryServer.name} component match! {compDictionaryServer.GetLog()}"); } } [UnityTest] public IEnumerator TestHashSetBuiltInTypeCollections() { var compHashSet = (HashSetBaseTypeTestHelper)null; var compHashSetServer = (HashSetBaseTypeTestHelper)null; var className = $"{nameof(HashSetBaseTypeTestHelper)}"; var clientList = m_ClientNetworkManagers.ToList(); if (m_ServerNetworkManager.IsHost) { clientList.Insert(0, m_ServerNetworkManager); } m_CurrentKey = 1000; foreach (var client in clientList) { /////////////////////////////////////////////////////////////////////////// // HashSet Single dimension list compHashSet = client.LocalClient.PlayerObject.GetComponent(); compHashSetServer = m_PlayerNetworkObjects[NetworkManager.ServerClientId][client.LocalClientId].GetComponent(); yield return WaitForConditionOrTimeOut(() => compHashSet.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compHashSet.OwnerClientId}'s {className} {compHashSet.name} component match! {compHashSet.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compHashSetServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compHashSetServer.OwnerClientId}'s {className} {compHashSetServer.name} component match! {compHashSetServer.GetLog()}"); ////////////////////////////////// // Owner Add Item compHashSet.Add(Random.Range(ushort.MinValue, ushort.MaxValue), ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compHashSet.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} add failed to synchronize on {className} {compHashSet.name}! {compHashSet.GetLog()}"); ////////////////////////////////// // Server Add Item compHashSetServer.Add(Random.Range(ushort.MinValue, ushort.MaxValue), ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compHashSetServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server add failed to synchronize on {className} {compHashSetServer.name}! {compHashSetServer.GetLog()}"); ////////////////////////////////// // Owner Remove Item var index = Random.Range(0, compHashSet.ListCollectionOwner.Value.Count - 1); compHashSet.Remove(compHashSet.ListCollectionOwner.Value.ElementAt(index), ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compHashSet.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} remove failed to synchronize on {className} {compHashSet.name}! {compHashSet.GetLog()}"); ////////////////////////////////// // Server Remove Item index = Random.Range(0, compHashSetServer.ListCollectionOwner.Value.Count - 1); compHashSetServer.Remove(compHashSetServer.ListCollectionOwner.Value.ElementAt(index), ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compHashSetServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server remove failed to synchronize on {className} {compHashSetServer.name}! {compHashSetServer.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compHashSet.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compHashSet.OwnerClientId}'s {className} {compHashSet.name} component match! {compHashSet.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compHashSetServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compHashSetServer.OwnerClientId}'s {className} {compHashSetServer.name} component match! {compHashSetServer.GetLog()}"); //////////////////////////////////// // Owner Full Set HashSet Values compHashSet.FullSet(HashSetBaseTypeTestHelper.GetHashSetValues(), ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compHashSet.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} full set failed to synchronize on {className} {compHashSet.name}! {compHashSet.GetLog()}"); ////////////////////////////////// // Server Full Set HashSet Values compHashSetServer.FullSet(HashSetBaseTypeTestHelper.GetHashSetValues(), ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compHashSetServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server full set failed to synchronize on {className} {compHashSetServer.name}! {compHashSetServer.GetLog()}"); //////////////////////////////////// // Owner Clear compHashSet.Clear(ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compHashSet.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} clear failed to synchronize on {className} {compHashSet.name}! {compHashSet.GetLog()}"); ////////////////////////////////// // Server Clear compHashSetServer.Clear(ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compHashSetServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server clear failed to synchronize on {className} {compHashSetServer.name}! {compHashSetServer.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compHashSet.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compHashSet.OwnerClientId}'s {className} {compHashSet.name} component match! {compHashSet.GetLog()}"); yield return WaitForConditionOrTimeOut(() => compHashSetServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compHashSetServer.OwnerClientId}'s {className} {compHashSetServer.name} component match! {compHashSetServer.GetLog()}"); } } } #region HASHSET COMPONENT HELPERS public class HashSetBaseTypeTestHelper : ListTestHelperBase, IHashSetTestHelperBase { public static Dictionary> Instances = new Dictionary>(); public static void ResetState() { Instances.Clear(); } public NetworkVariable> ListCollectionServer = new NetworkVariable>(new HashSet(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); public NetworkVariable> ListCollectionOwner = new NetworkVariable>(new HashSet(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); // This tracks what has changed per instance which is used to compare to all other instances public Dictionary>> NetworkVariableChanges = new Dictionary>>(); public bool ValidateInstances() { foreach (var clientId in NetworkManager.ConnectedClientsIds) { if (clientId == NetworkManager.LocalClientId) { continue; } if (!Instances.ContainsKey(clientId)) { return false; } if (!Instances[clientId].ContainsKey(NetworkObjectId)) { return false; } var otherOwnerCollection = Instances[clientId][NetworkObjectId].ListCollectionOwner; var otherServerCollection = Instances[clientId][NetworkObjectId].ListCollectionServer; if (!ListCollectionOwner.Value.SequenceEqual(otherOwnerCollection.Value)) { return false; } if (!ListCollectionServer.Value.SequenceEqual(otherServerCollection.Value)) { return false; } } return true; } private bool ChangesMatch(Dictionary> local, Dictionary> other) { var deltaTypes = Enum.GetValues(typeof(DeltaTypes)).OfType().ToList(); foreach (var deltaType in deltaTypes) { LogMessage($"Comparing {deltaType}:"); if (local[deltaType].Count != other[deltaType].Count) { LogMessage($"{deltaType}s did not match!"); return false; } foreach (var value in local[deltaType]) { if (!other[deltaType].Contains(value)) { LogMessage($"Value ({value}) in local was not found on remote!"); return false; } } } return true; } public override bool CompareTrackedChanges(Targets target) { LogStart(); var localChanges = NetworkVariableChanges[target]; foreach (var clientId in NetworkManager.ConnectedClientsIds) { if (clientId == NetworkManager.LocalClientId) { continue; } if (!Instances.ContainsKey(clientId)) { return false; } if (!Instances[clientId].ContainsKey(NetworkObjectId)) { return false; } var entry = Instances[clientId][NetworkObjectId]; var otherChanges = entry.NetworkVariableChanges[target]; LogMessage($"Comparing against client-{clientId} {entry.name}:"); if (!ChangesMatch(localChanges, otherChanges)) { LogMessage($"Client-{clientId} {entry.name} did not match!"); return false; } LogMessage($"Client-{clientId} {entry.name} matched!"); } return true; } public static HashSet GetHashSetValues(int count = 5) { var hashSet = new HashSet(); for (int i = 0; i < count; i++) { hashSet.Add(Random.Range(ushort.MinValue, ushort.MaxValue)); } return hashSet; } public NetworkVariable> GetNetVar(Targets target) { return target == Targets.Server ? ListCollectionServer : ListCollectionOwner; } public HashSet OnSetServerValues() { return GetHashSetValues(); } public HashSet OnSetOwnerValues() { return GetHashSetValues(); } public void Add(int value, Targets target) { var netVar = GetNetVar(target); netVar.Value.Add(value); netVar.CheckDirtyState(); } public void AddRange(HashSet values, Targets target) { var netVar = GetNetVar(target); foreach (var value in values) { netVar.Value.Add(value); } netVar.CheckDirtyState(); } public void Remove(int value, Targets target) { var netVar = GetNetVar(target); netVar.Value.Remove(value); netVar.CheckDirtyState(); } public void FullSet(HashSet values, Targets target) { var netVar = GetNetVar(target); netVar.Value = values; netVar.CheckDirtyState(); } public void Clear(Targets target) { var netVar = GetNetVar(target); netVar.Value.Clear(); netVar.CheckDirtyState(); } public void TrackChanges(Targets target, HashSet previous, HashSet current) { var contextTable = NetworkVariableChanges[target]; var whatWasAdded = current.Except(previous).ToHashSet(); var whatWasRemoved = previous.Where((c) => !current.Contains(c)).ToHashSet(); var whatWasNeitherAddedOrRemoved = current.Where((c) => previous.Contains(c) && !whatWasAdded.Contains(c)).ToHashSet(); var whatChanged = whatWasNeitherAddedOrRemoved.Where((c) => previous.Contains(c) && !previous.Where((d) => d.Equals(c)).FirstOrDefault().Equals(c)).ToHashSet(); var whatRemainedTheSame = whatWasNeitherAddedOrRemoved.Where((c) => !whatChanged.Contains(c)).ToHashSet(); contextTable[DeltaTypes.Added] = whatWasAdded; contextTable[DeltaTypes.Removed] = whatWasRemoved; contextTable[DeltaTypes.Changed] = whatChanged; contextTable[DeltaTypes.UnChanged] = whatRemainedTheSame; } public void OnServerListValuesChanged(HashSet previous, HashSet current) { TrackChanges(Targets.Server, previous, current); } public void OnOwnerListValuesChanged(HashSet previous, HashSet current) { TrackChanges(Targets.Owner, previous, current); } /// /// Keeps track of each client instsnce releative player instance with this component /// private void TrackRelativeInstances() { if (!Instances.ContainsKey(NetworkManager.LocalClientId)) { Instances.Add(NetworkManager.LocalClientId, new Dictionary()); } if (!Instances[NetworkManager.LocalClientId].ContainsKey(NetworkObjectId)) { Instances[NetworkManager.LocalClientId].Add(NetworkObjectId, this); } ResetTrackedChanges(); } public void ResetTrackedChanges() { NetworkVariableChanges.Clear(); NetworkVariableChanges.Add(Targets.Owner, new Dictionary>()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.Added, new HashSet()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.Changed, new HashSet()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.Removed, new HashSet()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.UnChanged, new HashSet()); NetworkVariableChanges.Add(Targets.Server, new Dictionary>()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.Added, new HashSet()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.Changed, new HashSet()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.Removed, new HashSet()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.UnChanged, new HashSet()); } protected override void OnNetworkPostSpawn() { TrackRelativeInstances(); ListCollectionServer.OnValueChanged += OnServerListValuesChanged; ListCollectionOwner.OnValueChanged += OnOwnerListValuesChanged; if (IsServer) { ListCollectionServer.Value = OnSetServerValues(); ListCollectionOwner.CheckDirtyState(); } if (IsOwner) { ListCollectionOwner.Value = OnSetOwnerValues(); ListCollectionOwner.CheckDirtyState(); } base.OnNetworkPostSpawn(); } public override void OnNetworkDespawn() { ListCollectionServer.OnValueChanged -= OnServerListValuesChanged; ListCollectionOwner.OnValueChanged -= OnOwnerListValuesChanged; base.OnNetworkDespawn(); } } #endregion #region DICTIONARY COMPONENT HELPERS public class NestedDictionaryTestHelper : ListTestHelperBase, IDictionaryTestHelperBase> { public static Dictionary> Instances = new Dictionary>(); public static void ResetState() { Instances.Clear(); } public NetworkVariable>> ListCollectionServer = new NetworkVariable>>(new Dictionary>(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); public NetworkVariable>> ListCollectionOwner = new NetworkVariable>>(new Dictionary>(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); // This tracks what has changed per instance which is used to compare to all other instances public Dictionary>>> NetworkVariableChanges = new Dictionary>>>(); private bool CompareDictionaries(ulong clientId, Dictionary first, Dictionary second) { foreach (var entry in first) { if (!second.ContainsKey(entry.Key)) { LogMessage($"Client-{clientId} has no key entry for ({entry.Key})!"); return false; } var seconValue = second[entry.Key]; if (!entry.Value.Equals(seconValue)) { LogMessage($"Client-{clientId} value ({seconValue} does not equal ({entry.Value})!"); return false; } } return true; } private bool CompareNestedDictionaries(ulong clientId, Dictionary> first, Dictionary> second) { foreach (var entry in first) { if (!second.ContainsKey(entry.Key)) { LogMessage($"Client-{clientId} has no key entry for ({entry.Key})!"); return false; } var secondValue = second[entry.Key]; if (!CompareDictionaries(clientId, entry.Value, secondValue)) { LogMessage($"Client-{clientId} value root Key ({entry.Key}) dictionary does not equal the local dictionary!"); return false; } } return true; } public bool ValidateInstances() { LogStart(); foreach (var clientId in NetworkManager.ConnectedClientsIds) { if (clientId == NetworkManager.LocalClientId) { continue; } if (!Instances.ContainsKey(clientId)) { LogMessage($"Client-{clientId} has no entry!"); return false; } if (!Instances[clientId].ContainsKey(NetworkObjectId)) { LogMessage($"Client-{clientId} has no instance entry of NetworkObject ({NetworkObjectId})!"); return false; } var otherOwnerCollection = Instances[clientId][NetworkObjectId].ListCollectionOwner; var otherServerCollection = Instances[clientId][NetworkObjectId].ListCollectionServer; if (!CompareNestedDictionaries(clientId, ListCollectionOwner.Value, otherOwnerCollection.Value)) { LogMessage($"Client-{clientId} did not synchronize properly with the owner collection!"); return false; } if (!CompareNestedDictionaries(clientId, ListCollectionServer.Value, otherServerCollection.Value)) { LogMessage($"Client-{clientId} did not synchronize properly with the server collection!"); return false; } } return true; } private bool ChangesMatch(ulong clientId, Dictionary>> local, Dictionary>> other) { var deltaTypes = Enum.GetValues(typeof(DeltaTypes)).OfType().ToList(); foreach (var deltaType in deltaTypes) { LogMessage($"Comparing {deltaType}:"); if (local[deltaType].Count != other[deltaType].Count) { LogMessage($"{deltaType}s count did not match!"); return false; } if (!CompareNestedDictionaries(clientId, local[deltaType], other[deltaType])) { LogMessage($"{deltaType}s values did not match!"); return false; } } return true; } public override bool CompareTrackedChanges(Targets target) { LogStart(); var localChanges = NetworkVariableChanges[target]; foreach (var clientId in NetworkManager.ConnectedClientsIds) { if (clientId == NetworkManager.LocalClientId) { continue; } if (!Instances.ContainsKey(clientId)) { return false; } if (!Instances[clientId].ContainsKey(NetworkObjectId)) { return false; } var entry = Instances[clientId][NetworkObjectId]; var otherChanges = entry.NetworkVariableChanges[target]; LogMessage($"Comparing against client-{clientId} {entry.name}:"); if (!ChangesMatch(clientId, localChanges, otherChanges)) { LogMessage($"Client-{clientId} {entry.name} failed to synchronize properly!"); return false; } LogMessage($"Client-{clientId} {entry.name} matched!"); } return true; } public static Dictionary GetDictionaryValues(int count = 5) { var dictionary = new Dictionary(); for (int i = 0; i < count; i++) { dictionary.Add(i, SerializableObject.GetRandomObject()); } return dictionary; } public static Dictionary> GetNestedDictionaryValues(int count = 5) { var dictionary = new Dictionary>(); for (int i = 0; i < count; i++) { dictionary.Add(i, GetDictionaryValues()); } return dictionary; } public NetworkVariable>> GetNetVar(Targets target) { return target == Targets.Server ? ListCollectionServer : ListCollectionOwner; } public Dictionary> OnSetServerValues() { return GetNestedDictionaryValues(); } public Dictionary> OnSetOwnerValues() { return GetNestedDictionaryValues(); } public bool UpdateValue((int, Dictionary) value, Targets target, bool checkDirty = true) { var netVar = GetNetVar(target); if (netVar.Value.ContainsKey(value.Item1)) { netVar.Value[value.Item1] = value.Item2; if (checkDirty) { netVar.CheckDirtyState(); } return true; } return false; } public void Add((int, Dictionary) value, Targets target) { var netVar = GetNetVar(target); netVar.Value.Add(value.Item1, value.Item2); netVar.CheckDirtyState(); } public void Remove(int key, Targets target) { var netVar = GetNetVar(target); netVar.Value.Remove(key); netVar.CheckDirtyState(); } public void FullSet(Dictionary> values, Targets target) { var netVar = GetNetVar(target); netVar.Value = values; netVar.CheckDirtyState(); } public void Clear(Targets target) { var netVar = GetNetVar(target); netVar.Value.Clear(); netVar.CheckDirtyState(); } public void TrackChanges(Targets target, Dictionary> previous, Dictionary> current) { var contextTable = NetworkVariableChanges[target]; var whatWasAdded = current.Except(previous).ToDictionary(item => item.Key, item => item.Value); var whatWasRemoved = previous.Where((c) => !current.Contains(c)).ToDictionary(item => item.Key, item => item.Value); var whatWasNeitherAddedOrRemoved = current.Where((c) => previous.Contains(c) && !whatWasAdded.Contains(c)).ToDictionary(item => item.Key, item => item.Value); var whatChanged = whatWasNeitherAddedOrRemoved.Where((c) => previous.Contains(c) && !previous.Where((d) => d.Equals(c)).FirstOrDefault().Equals(c)).ToDictionary(item => item.Key, item => item.Value); var whatRemainedTheSame = whatWasNeitherAddedOrRemoved.Where((c) => !whatChanged.Contains(c)).ToDictionary(item => item.Key, item => item.Value); contextTable[DeltaTypes.Added] = whatWasAdded; contextTable[DeltaTypes.Removed] = whatWasRemoved; contextTable[DeltaTypes.Changed] = whatChanged; contextTable[DeltaTypes.UnChanged] = whatRemainedTheSame; } public void OnServerListValuesChanged(Dictionary> previous, Dictionary> current) { TrackChanges(Targets.Server, previous, current); } public void OnOwnerListValuesChanged(Dictionary> previous, Dictionary> current) { TrackChanges(Targets.Owner, previous, current); } /// /// Keeps track of each client instsnce releative player instance with this component /// private void TrackRelativeInstances() { if (!Instances.ContainsKey(NetworkManager.LocalClientId)) { Instances.Add(NetworkManager.LocalClientId, new Dictionary()); } if (!Instances[NetworkManager.LocalClientId].ContainsKey(NetworkObjectId)) { Instances[NetworkManager.LocalClientId].Add(NetworkObjectId, this); } ResetTrackedChanges(); } public void ResetTrackedChanges() { NetworkVariableChanges.Clear(); NetworkVariableChanges.Add(Targets.Owner, new Dictionary>>()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.Added, new Dictionary>()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.Changed, new Dictionary>()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.Removed, new Dictionary>()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.UnChanged, new Dictionary>()); NetworkVariableChanges.Add(Targets.Server, new Dictionary>>()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.Added, new Dictionary>()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.Changed, new Dictionary>()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.Removed, new Dictionary>()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.UnChanged, new Dictionary>()); } protected override void OnNetworkPostSpawn() { TrackRelativeInstances(); ListCollectionServer.OnValueChanged += OnServerListValuesChanged; ListCollectionOwner.OnValueChanged += OnOwnerListValuesChanged; if (IsServer) { ListCollectionServer.Value = OnSetServerValues(); ListCollectionOwner.CheckDirtyState(); } if (IsOwner) { ListCollectionOwner.Value = OnSetOwnerValues(); ListCollectionOwner.CheckDirtyState(); } base.OnNetworkPostSpawn(); } public override void OnNetworkDespawn() { ListCollectionServer.OnValueChanged -= OnServerListValuesChanged; ListCollectionOwner.OnValueChanged -= OnOwnerListValuesChanged; base.OnNetworkDespawn(); } } public class DictionaryTestHelper : ListTestHelperBase, IDictionaryTestHelperBase { public static Dictionary> Instances = new Dictionary>(); public static void ResetState() { Instances.Clear(); } public NetworkVariable> ListCollectionServer = new NetworkVariable>(new Dictionary(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); public NetworkVariable> ListCollectionOwner = new NetworkVariable>(new Dictionary(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); // This tracks what has changed per instance which is used to compare to all other instances public Dictionary>> NetworkVariableChanges = new Dictionary>>(); private bool CompareDictionaries(ulong clientId, Dictionary first, Dictionary second) { foreach (var entry in first) { if (!second.ContainsKey(entry.Key)) { LogMessage($"Client-{clientId} has no key entry for ({entry.Key})!"); return false; } var seconValue = second[entry.Key]; if (!entry.Value.Equals(seconValue)) { LogMessage($"Client-{clientId} value ({seconValue} does not equal ({entry.Value})!"); return false; } } return true; } public bool ValidateInstances() { LogStart(); foreach (var clientId in NetworkManager.ConnectedClientsIds) { if (clientId == NetworkManager.LocalClientId) { continue; } if (!Instances.ContainsKey(clientId)) { LogMessage($"Client-{clientId} has no entry!"); return false; } if (!Instances[clientId].ContainsKey(NetworkObjectId)) { LogMessage($"Client-{clientId} has no instance entry of NetworkObject ({NetworkObjectId})!"); return false; } var otherOwnerCollection = Instances[clientId][NetworkObjectId].ListCollectionOwner; var otherServerCollection = Instances[clientId][NetworkObjectId].ListCollectionServer; if (!CompareDictionaries(clientId, ListCollectionOwner.Value, otherOwnerCollection.Value)) { LogMessage($"Client-{clientId} did not synchronize properly with the owner collection!"); return false; } if (!CompareDictionaries(clientId, ListCollectionServer.Value, otherServerCollection.Value)) { LogMessage($"Client-{clientId} did not synchronize properly with the server collection!"); return false; } } return true; } private bool ChangesMatch(ulong clientId, Dictionary> local, Dictionary> other) { var deltaTypes = Enum.GetValues(typeof(DeltaTypes)).OfType().ToList(); foreach (var deltaType in deltaTypes) { LogMessage($"Comparing {deltaType}:"); if (local[deltaType].Count != other[deltaType].Count) { LogMessage($"{deltaType}s count did not match!"); return false; } if (!CompareDictionaries(clientId, local[deltaType], other[deltaType])) { LogMessage($"{deltaType}s values did not match!"); return false; } } return true; } public override bool CompareTrackedChanges(Targets target) { LogStart(); var localChanges = NetworkVariableChanges[target]; foreach (var clientId in NetworkManager.ConnectedClientsIds) { if (clientId == NetworkManager.LocalClientId) { continue; } if (!Instances.ContainsKey(clientId)) { return false; } if (!Instances[clientId].ContainsKey(NetworkObjectId)) { return false; } var entry = Instances[clientId][NetworkObjectId]; var otherChanges = entry.NetworkVariableChanges[target]; LogMessage($"Comparing against client-{clientId} {entry.name}:"); if (!ChangesMatch(clientId, localChanges, otherChanges)) { LogMessage($"Client-{clientId} {entry.name} failed to synchronize properly!"); return false; } LogMessage($"Client-{clientId} {entry.name} matched!"); } return true; } public static Dictionary GetDictionaryValues(int count = 5) { var dictionary = new Dictionary(); for (int i = 0; i < count; i++) { dictionary.Add(i, SerializableObject.GetRandomObject()); } return dictionary; } public NetworkVariable> GetNetVar(Targets target) { return target == Targets.Server ? ListCollectionServer : ListCollectionOwner; } public Dictionary OnSetServerValues() { return GetDictionaryValues(); } public Dictionary OnSetOwnerValues() { return GetDictionaryValues(); } public bool UpdateValue((int, SerializableObject) value, Targets target, bool checkDirty = true) { var netVar = GetNetVar(target); if (netVar.Value.ContainsKey(value.Item1)) { netVar.Value[value.Item1] = value.Item2; if (checkDirty) { netVar.CheckDirtyState(); } return true; } return false; } public void Add((int, SerializableObject) value, Targets target) { var netVar = GetNetVar(target); netVar.Value.Add(value.Item1, value.Item2); netVar.CheckDirtyState(); } public void Remove(int key, Targets target) { var netVar = GetNetVar(target); netVar.Value.Remove(key); netVar.CheckDirtyState(); } public void FullSet(Dictionary values, Targets target) { var netVar = GetNetVar(target); netVar.Value = values; netVar.CheckDirtyState(); } public void Clear(Targets target) { var netVar = GetNetVar(target); netVar.Value.Clear(); netVar.CheckDirtyState(); } public void TrackChanges(Targets target, Dictionary previous, Dictionary current) { var contextTable = NetworkVariableChanges[target]; var whatWasAdded = current.Except(previous).ToDictionary(item => item.Key, item => item.Value); var whatWasRemoved = previous.Where((c) => !current.Contains(c)).ToDictionary(item => item.Key, item => item.Value); var whatWasNeitherAddedOrRemoved = current.Where((c) => previous.Contains(c) && !whatWasAdded.Contains(c)).ToDictionary(item => item.Key, item => item.Value); var whatChanged = whatWasNeitherAddedOrRemoved.Where((c) => previous.Contains(c) && !previous.Where((d) => d.Equals(c)).FirstOrDefault().Equals(c)).ToDictionary(item => item.Key, item => item.Value); var whatRemainedTheSame = whatWasNeitherAddedOrRemoved.Where((c) => !whatChanged.Contains(c)).ToDictionary(item => item.Key, item => item.Value); contextTable[DeltaTypes.Added] = whatWasAdded; contextTable[DeltaTypes.Removed] = whatWasRemoved; contextTable[DeltaTypes.Changed] = whatChanged; contextTable[DeltaTypes.UnChanged] = whatRemainedTheSame; } public void OnServerListValuesChanged(Dictionary previous, Dictionary current) { TrackChanges(Targets.Server, previous, current); } public void OnOwnerListValuesChanged(Dictionary previous, Dictionary current) { TrackChanges(Targets.Owner, previous, current); } /// /// Keeps track of each client instsnce releative player instance with this component /// private void TrackRelativeInstances() { if (!Instances.ContainsKey(NetworkManager.LocalClientId)) { Instances.Add(NetworkManager.LocalClientId, new Dictionary()); } if (!Instances[NetworkManager.LocalClientId].ContainsKey(NetworkObjectId)) { Instances[NetworkManager.LocalClientId].Add(NetworkObjectId, this); } ResetTrackedChanges(); } public void ResetTrackedChanges() { NetworkVariableChanges.Clear(); NetworkVariableChanges.Add(Targets.Owner, new Dictionary>()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.Added, new Dictionary()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.Changed, new Dictionary()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.Removed, new Dictionary()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.UnChanged, new Dictionary()); NetworkVariableChanges.Add(Targets.Server, new Dictionary>()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.Added, new Dictionary()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.Changed, new Dictionary()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.Removed, new Dictionary()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.UnChanged, new Dictionary()); } protected override void OnNetworkPostSpawn() { TrackRelativeInstances(); ListCollectionServer.OnValueChanged += OnServerListValuesChanged; ListCollectionOwner.OnValueChanged += OnOwnerListValuesChanged; if (IsServer) { ListCollectionServer.Value = OnSetServerValues(); ListCollectionOwner.CheckDirtyState(); } if (IsOwner) { ListCollectionOwner.Value = OnSetOwnerValues(); ListCollectionOwner.CheckDirtyState(); } base.OnNetworkPostSpawn(); } public override void OnNetworkDespawn() { ListCollectionServer.OnValueChanged -= OnServerListValuesChanged; ListCollectionOwner.OnValueChanged -= OnOwnerListValuesChanged; base.OnNetworkDespawn(); } } #endregion #region INETWORKSERIALIZABLE LIST TEST COMPONENT HELPERS public class SerializableObject : INetworkSerializable, IEquatable { public static SerializableObject GetRandomObject() { var serializableObject = new SerializableObject() { FloatValue = Random.Range(float.MinValue, float.MaxValue), IntValue = Random.Range(ushort.MinValue, ushort.MaxValue), LongValue = Random.Range(int.MinValue, int.MaxValue), }; return serializableObject; } public static List GetListOfRandomObjects(int count) { var list = new List(); for (int i = 0; i < count; i++) { list.Add(GetRandomObject()); } return list; } public static List> GetListOfListOfRandomObjects(int numberOfLists, int countPerList) { var list = new List>(); for (int i = 0; i < numberOfLists; i++) { list.Add(GetListOfRandomObjects(countPerList)); } return list; } public int IntValue; public long LongValue; public float FloatValue; public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { serializer.SerializeValue(ref IntValue); serializer.SerializeValue(ref LongValue); serializer.SerializeValue(ref FloatValue); } public bool Equals(SerializableObject other) { return IntValue.Equals(other.IntValue) && LongValue.Equals(other.LongValue) && FloatValue.Equals(other.FloatValue); } } public class ListTestHelperListSerializableObject : ListTestHelperBase, IListTestHelperBase> { public static Dictionary> Instances = new Dictionary>(); public static void ResetState() { Instances.Clear(); } public NetworkVariable>> ListCollectionServer = new NetworkVariable>>(new List>(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); public NetworkVariable>> ListCollectionOwner = new NetworkVariable>>(new List>(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); // This tracks what has changed per instance which is used to compare to all other instances public Dictionary>>> NetworkVariableChanges = new Dictionary>>>(); public bool ValidateInstances() { foreach (var clientId in NetworkManager.ConnectedClientsIds) { if (clientId == NetworkManager.LocalClientId) { continue; } if (!Instances.ContainsKey(clientId)) { return false; } if (!Instances[clientId].ContainsKey(NetworkObjectId)) { return false; } var otherOwnerCollection = Instances[clientId][NetworkObjectId].ListCollectionOwner; var otherServerCollection = Instances[clientId][NetworkObjectId].ListCollectionServer; if (!CompareBothItems(ListCollectionOwner.Value, otherOwnerCollection.Value)) { return false; } if (!CompareBothItems(ListCollectionServer.Value, otherServerCollection.Value)) { return false; } } return true; } private bool CompareBothItems(List> first, List> second) { if (first.Count != second.Count) { LogMessage($"Local count ({first.Count}) did not match remote count ({second.Count})!"); return false; } for (int i = 0; i < first.Count; i++) { if (!first[i].SequenceEqual(second[i])) { LogMessage($"Sequence set ({i}) does not match! Local[{i}].Count = {first[i].Count} Remote[{i}].Count = {second[i].Count}."); if (first[i].Count == second[i].Count) { var subBuilder = new StringBuilder(); for (int j = 0; j < first[i].Count; j++) { subBuilder.Append($"[{first[i][j]}][{second[i][j]}]"); } LogMessage($"Compared: {subBuilder}"); } return false; } } return true; } private bool ChangesMatch(Dictionary>> local, Dictionary>> other) { var deltaTypes = Enum.GetValues(typeof(DeltaTypes)).OfType().ToList(); foreach (var deltaType in deltaTypes) { LogMessage($"Comparing {deltaType}:"); if (!CompareBothItems(local[deltaType], other[deltaType])) { LogMessage($"{deltaType}s did not match!"); return false; } } return true; } public override bool CompareTrackedChanges(Targets target) { LogStart(); var localChanges = NetworkVariableChanges[target]; var trackChangesSuccess = true; foreach (var clientId in NetworkManager.ConnectedClientsIds) { if (clientId == NetworkManager.LocalClientId) { continue; } if (!Instances.ContainsKey(clientId)) { trackChangesSuccess = false; break; } if (!Instances[clientId].ContainsKey(NetworkObjectId)) { trackChangesSuccess = false; break; } var entry = Instances[clientId][NetworkObjectId]; var otherChanges = entry.NetworkVariableChanges[target]; LogMessage($"Comparing against client-{clientId} {entry.name}:"); if (!ChangesMatch(localChanges, otherChanges)) { LogMessage($"Client-{clientId} {entry.name} did not match!"); trackChangesSuccess = false; break; } LogMessage($"Client-{clientId} {entry.name} matched!"); } return trackChangesSuccess; } private List> GetInitialValues() { var rootList = new List>(); for (int i = 0; i < 10; i++) { rootList.Add(SerializableObject.GetListOfRandomObjects(5)); } return rootList; } public NetworkVariable>> GetNetVar(Targets target) { return target == Targets.Server ? ListCollectionServer : ListCollectionOwner; } public List> OnSetServerValues() { return GetInitialValues(); } public List> OnSetOwnerValues() { return GetInitialValues(); } public void UpdateValue(List value, Targets target, bool checkDirty = true) { var netVar = GetNetVar(target); var index = netVar.Value.IndexOf(value); netVar.Value[index] = value; if (checkDirty) { netVar.CheckDirtyState(); } } public void Add(List value, Targets target) { var netVar = GetNetVar(target); netVar.Value.Add(value); netVar.CheckDirtyState(); } public void AddRange(List> values, Targets target) { var netVar = GetNetVar(target); netVar.Value.AddRange(values); netVar.CheckDirtyState(); } public void Insert(List value, int index, Targets target, bool checkDirty = true) { var netVar = GetNetVar(target); netVar.Value.Insert(index, value); if (checkDirty) { netVar.CheckDirtyState(); } } public void Remove(List value, Targets target) { var netVar = GetNetVar(target); netVar.Value.Remove(value); netVar.CheckDirtyState(); } public void FullSet(List> values, Targets target) { var netVar = GetNetVar(target); netVar.Value = values; netVar.CheckDirtyState(); } public void Clear(Targets target) { var netVar = GetNetVar(target); netVar.Value.Clear(); netVar.CheckDirtyState(); } public void TrackChanges(Targets target, List> previous, List> current) { var contextTable = NetworkVariableChanges[target]; var whatWasAdded = current.Except(previous).ToList(); var whatWasRemoved = previous.Where((c) => !current.Contains(c)).ToList(); contextTable[DeltaTypes.Added] = whatWasAdded; contextTable[DeltaTypes.Removed] = whatWasRemoved; contextTable[DeltaTypes.Changed].Clear(); contextTable[DeltaTypes.UnChanged].Clear(); for (int i = 0; i < current.Count; i++) { if (previous.Count > i && !current[i].SequenceEqual(previous[i])) { contextTable[DeltaTypes.Changed].Add(current[i]); } else if (!whatWasAdded.Contains(current[i]) && previous.Contains(current[i])) { contextTable[DeltaTypes.UnChanged].Add(current[i]); } } } public void OnServerListValuesChanged(List> previous, List> current) { TrackChanges(Targets.Server, previous, current); } public void OnOwnerListValuesChanged(List> previous, List> current) { TrackChanges(Targets.Owner, previous, current); } /// /// Keeps track of each client instsnce releative player instance with this component /// private void TrackRelativeInstances() { if (!Instances.ContainsKey(NetworkManager.LocalClientId)) { Instances.Add(NetworkManager.LocalClientId, new Dictionary()); } if (!Instances[NetworkManager.LocalClientId].ContainsKey(NetworkObjectId)) { Instances[NetworkManager.LocalClientId].Add(NetworkObjectId, this); } ResetTrackedChanges(); } public void ResetTrackedChanges() { NetworkVariableChanges.Clear(); NetworkVariableChanges.Add(Targets.Owner, new Dictionary>>()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.Added, new List>()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.Changed, new List>()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.Removed, new List>()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.UnChanged, new List>()); NetworkVariableChanges.Add(Targets.Server, new Dictionary>>()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.Added, new List>()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.Changed, new List>()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.Removed, new List>()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.UnChanged, new List>()); } protected override void OnNetworkPostSpawn() { TrackRelativeInstances(); ListCollectionServer.OnValueChanged += OnServerListValuesChanged; ListCollectionOwner.OnValueChanged += OnOwnerListValuesChanged; if (IsServer) { ListCollectionServer.Value = OnSetServerValues(); ListCollectionServer.CheckDirtyState(); } if (IsOwner) { ListCollectionOwner.Value = OnSetOwnerValues(); ListCollectionOwner.CheckDirtyState(); } base.OnNetworkPostSpawn(); } public override void OnNetworkDespawn() { ListCollectionServer.OnValueChanged -= OnServerListValuesChanged; ListCollectionOwner.OnValueChanged -= OnOwnerListValuesChanged; base.OnNetworkDespawn(); } } public class ListTestHelperSerializableObject : ListTestHelperBase, IListTestHelperBase { public static Dictionary> Instances = new Dictionary>(); public static void ResetState() { Instances.Clear(); } public NetworkVariable> ListCollectionServer = new NetworkVariable>(new List(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); public NetworkVariable> ListCollectionOwner = new NetworkVariable>(new List(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); // This tracks what has changed per instance which is used to compare to all other instances public Dictionary>> NetworkVariableChanges = new Dictionary>>(); public bool ValidateInstances() { foreach (var clientId in NetworkManager.ConnectedClientsIds) { if (clientId == NetworkManager.LocalClientId) { continue; } if (!Instances.ContainsKey(clientId)) { return false; } if (!Instances[clientId].ContainsKey(NetworkObjectId)) { return false; } var otherOwnerCollection = Instances[clientId][NetworkObjectId].ListCollectionOwner; var otherServerCollection = Instances[clientId][NetworkObjectId].ListCollectionServer; if (!ListCollectionOwner.Value.SequenceEqual(otherOwnerCollection.Value)) { return false; } if (!ListCollectionServer.Value.SequenceEqual(otherServerCollection.Value)) { return false; } } return true; } private bool ChangesMatch(Dictionary> local, Dictionary> other) { var deltaTypes = Enum.GetValues(typeof(DeltaTypes)).OfType().ToList(); foreach (var deltaType in deltaTypes) { LogMessage($"Comparing {deltaType}:"); if (local[deltaType].Count != other[deltaType].Count) { LogMessage($"{deltaType}s did not match!"); return false; } for (int i = 0; i < local[deltaType].Count; i++) { if (!local[deltaType][i].Equals(other[deltaType][i])) { LogMessage($"Sequence set ({i}) does not match! Local[{i}] = {local[deltaType][i]} Remote[{i}].Count = {other[deltaType][i]}."); return false; } } } return true; } public override bool CompareTrackedChanges(Targets target) { LogStart(); var localChanges = NetworkVariableChanges[target]; foreach (var clientId in NetworkManager.ConnectedClientsIds) { if (clientId == NetworkManager.LocalClientId) { continue; } if (!Instances.ContainsKey(clientId)) { return false; } if (!Instances[clientId].ContainsKey(NetworkObjectId)) { return false; } var entry = Instances[clientId][NetworkObjectId]; var otherChanges = entry.NetworkVariableChanges[target]; LogMessage($"Comparing against client-{clientId} {entry.name}:"); if (!ChangesMatch(localChanges, otherChanges)) { LogMessage($"Client-{clientId} {entry.name} did not match!"); return false; } LogMessage($"Client-{clientId} {entry.name} matched!"); } return true; } private List GetInitialValues() { return SerializableObject.GetListOfRandomObjects(10); } public NetworkVariable> GetNetVar(Targets target) { return target == Targets.Server ? ListCollectionServer : ListCollectionOwner; } public List OnSetServerValues() { return GetInitialValues(); } public List OnSetOwnerValues() { return GetInitialValues(); } public void UpdateValue(SerializableObject value, Targets target, bool checkDirty = true) { var netVar = GetNetVar(target); var index = netVar.Value.IndexOf(value); netVar.Value[index] = value; if (checkDirty) { netVar.CheckDirtyState(); } } public void Add(SerializableObject value, Targets target) { var netVar = GetNetVar(target); netVar.Value.Add(value); netVar.CheckDirtyState(); } public void AddRange(List values, Targets target) { var netVar = GetNetVar(target); netVar.Value.AddRange(values); netVar.CheckDirtyState(); } public void Insert(SerializableObject value, int index, Targets target, bool checkDirty = true) { var netVar = GetNetVar(target); netVar.Value.Insert(index, value); if (checkDirty) { netVar.CheckDirtyState(); } } public void Remove(SerializableObject value, Targets target) { var netVar = GetNetVar(target); netVar.Value.Remove(value); netVar.CheckDirtyState(); } public void FullSet(List values, Targets target) { var netVar = GetNetVar(target); netVar.Value = values; netVar.CheckDirtyState(); } public void Clear(Targets target) { var netVar = GetNetVar(target); netVar.Value.Clear(); netVar.CheckDirtyState(); } public void TrackChanges(Targets target, List previous, List current) { var contextTable = NetworkVariableChanges[target]; var whatWasAdded = current.Except(previous).ToList(); var whatWasRemoved = previous.Where((c) => !current.Contains(c)).ToList(); var whatWasNeitherAddedOrRemoved = current.Where((c) => previous.Contains(c) && !whatWasAdded.Contains(c)).ToList(); var whatChanged = whatWasNeitherAddedOrRemoved.Where((c) => previous.Contains(c) && !previous.Where((d) => d.Equals(c)).FirstOrDefault().Equals(c)).ToList(); var whatRemainedTheSame = whatWasNeitherAddedOrRemoved.Where((c) => !whatChanged.Contains(c)).ToList(); contextTable[DeltaTypes.Added] = whatWasAdded; contextTable[DeltaTypes.Removed] = whatWasRemoved; contextTable[DeltaTypes.Changed] = whatChanged; contextTable[DeltaTypes.UnChanged] = whatRemainedTheSame; } public void OnServerListValuesChanged(List previous, List current) { TrackChanges(Targets.Server, previous, current); } public void OnOwnerListValuesChanged(List previous, List current) { TrackChanges(Targets.Owner, previous, current); } /// /// Keeps track of each client instsnce releative player instance with this component /// private void TrackRelativeInstances() { if (!Instances.ContainsKey(NetworkManager.LocalClientId)) { Instances.Add(NetworkManager.LocalClientId, new Dictionary()); } if (!Instances[NetworkManager.LocalClientId].ContainsKey(NetworkObjectId)) { Instances[NetworkManager.LocalClientId].Add(NetworkObjectId, this); } ResetTrackedChanges(); } public void ResetTrackedChanges() { NetworkVariableChanges.Clear(); NetworkVariableChanges.Add(Targets.Owner, new Dictionary>()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.Added, new List()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.Changed, new List()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.Removed, new List()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.UnChanged, new List()); NetworkVariableChanges.Add(Targets.Server, new Dictionary>()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.Added, new List()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.Changed, new List()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.Removed, new List()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.UnChanged, new List()); } protected override void OnNetworkPostSpawn() { TrackRelativeInstances(); ListCollectionServer.OnValueChanged += OnServerListValuesChanged; ListCollectionOwner.OnValueChanged += OnOwnerListValuesChanged; if (IsServer) { ListCollectionServer.Value = OnSetServerValues(); ListCollectionOwner.CheckDirtyState(); } if (IsOwner) { ListCollectionOwner.Value = OnSetOwnerValues(); ListCollectionOwner.CheckDirtyState(); } base.OnNetworkPostSpawn(); } public override void OnNetworkDespawn() { ListCollectionServer.OnValueChanged -= OnServerListValuesChanged; ListCollectionOwner.OnValueChanged -= OnOwnerListValuesChanged; base.OnNetworkDespawn(); } } #endregion #region BUILT-IN LIST TEST COMPONENT HELPERS public class ListTestHelperListInt : ListTestHelperBase, IListTestHelperBase> { public static Dictionary> Instances = new Dictionary>(); public static void ResetState() { Instances.Clear(); } public NetworkVariable>> ListCollectionServer = new NetworkVariable>>(new List>(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); public NetworkVariable>> ListCollectionOwner = new NetworkVariable>>(new List>(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); // This tracks what has changed per instance which is used to compare to all other instances public Dictionary>>> NetworkVariableChanges = new Dictionary>>>(); public bool ValidateInstances() { foreach (var clientId in NetworkManager.ConnectedClientsIds) { if (clientId == NetworkManager.LocalClientId) { continue; } if (!Instances.ContainsKey(clientId)) { return false; } if (!Instances[clientId].ContainsKey(NetworkObjectId)) { return false; } var otherOwnerCollection = Instances[clientId][NetworkObjectId].ListCollectionOwner; var otherServerCollection = Instances[clientId][NetworkObjectId].ListCollectionServer; if (!CompareBothItems(ListCollectionOwner.Value, otherOwnerCollection.Value)) { return false; } if (!CompareBothItems(ListCollectionServer.Value, otherServerCollection.Value)) { return false; } } return true; } private bool CompareBothItems(List> first, List> second) { if (first.Count != second.Count) { LogMessage($"Local count ({first.Count}) did not match remote count ({second.Count})!"); return false; } for (int i = 0; i < first.Count; i++) { if (!first[i].SequenceEqual(second[i])) { LogMessage($"Sequence set ({i}) does not match! Local[{i}].Count = {first[i].Count} Remote[{i}].Count = {second[i].Count}."); if (first[i].Count == second[i].Count) { var subBuilder = new StringBuilder(); for (int j = 0; j < first[i].Count; j++) { subBuilder.Append($"[{first[i][j]}][{second[i][j]}]"); } LogMessage($"Compared: {subBuilder}"); } return false; } } return true; } private bool ChangesMatch(Dictionary>> local, Dictionary>> other) { var deltaTypes = Enum.GetValues(typeof(DeltaTypes)).OfType().ToList(); foreach (var deltaType in deltaTypes) { LogMessage($"Comparing {deltaType}:"); if (!CompareBothItems(local[deltaType], other[deltaType])) { LogMessage($"{deltaType}s did not match!"); return false; } } return true; } public override bool CompareTrackedChanges(Targets target) { LogStart(); var localChanges = NetworkVariableChanges[target]; var trackChangesSuccess = true; foreach (var clientId in NetworkManager.ConnectedClientsIds) { if (clientId == NetworkManager.LocalClientId) { continue; } if (!Instances.ContainsKey(clientId)) { trackChangesSuccess = false; break; } if (!Instances[clientId].ContainsKey(NetworkObjectId)) { trackChangesSuccess = false; break; } var entry = Instances[clientId][NetworkObjectId]; var otherChanges = entry.NetworkVariableChanges[target]; LogMessage($"Comparing against client-{clientId} {entry.name}:"); if (!ChangesMatch(localChanges, otherChanges)) { LogMessage($"Client-{clientId} {entry.name} did not match!"); trackChangesSuccess = false; break; } LogMessage($"Client-{clientId} {entry.name} matched!"); } return trackChangesSuccess; } private List> GetInitialValues() { var rootList = new List>(); for (int i = 0; i < 10; i++) { var childList = new List(); for (int j = 0; j < 10; j++) { childList.Add(Random.Range(short.MinValue, short.MaxValue)); } rootList.Add(childList); } return rootList; } public NetworkVariable>> GetNetVar(Targets target) { return target == Targets.Server ? ListCollectionServer : ListCollectionOwner; } public List> OnSetServerValues() { return GetInitialValues(); } public List> OnSetOwnerValues() { return GetInitialValues(); } public void UpdateValue(List value, Targets target, bool checkDirty = true) { var netVar = GetNetVar(target); var index = netVar.Value.IndexOf(value); netVar.Value[index] = value; if (checkDirty) { netVar.CheckDirtyState(); } } public void Add(List value, Targets target) { var netVar = GetNetVar(target); netVar.Value.Add(value); netVar.CheckDirtyState(); } public void AddRange(List> values, Targets target) { var netVar = GetNetVar(target); netVar.Value.AddRange(values); netVar.CheckDirtyState(); } public void Insert(List value, int index, Targets target, bool checkDirty = true) { var netVar = GetNetVar(target); netVar.Value.Insert(index, value); if (checkDirty) { netVar.CheckDirtyState(); } } public void Remove(List value, Targets target) { var netVar = GetNetVar(target); netVar.Value.Remove(value); netVar.CheckDirtyState(); } public void FullSet(List> values, Targets target) { var netVar = GetNetVar(target); netVar.Value = values; netVar.CheckDirtyState(); } public void Clear(Targets target) { var netVar = GetNetVar(target); netVar.Value.Clear(); netVar.CheckDirtyState(); } public void TrackChanges(Targets target, List> previous, List> current) { var contextTable = NetworkVariableChanges[target]; var whatWasAdded = current.Except(previous).ToList(); var whatWasRemoved = previous.Where((c) => !current.Contains(c)).ToList(); contextTable[DeltaTypes.Added] = whatWasAdded; contextTable[DeltaTypes.Removed] = whatWasRemoved; contextTable[DeltaTypes.Changed].Clear(); contextTable[DeltaTypes.UnChanged].Clear(); for (int i = 0; i < current.Count; i++) { if (previous.Count > i && !current[i].SequenceEqual(previous[i])) { contextTable[DeltaTypes.Changed].Add(current[i]); } else if (!whatWasAdded.Contains(current[i]) && previous.Contains(current[i])) { contextTable[DeltaTypes.UnChanged].Add(current[i]); } } } public void OnServerListValuesChanged(List> previous, List> current) { TrackChanges(Targets.Server, previous, current); } public void OnOwnerListValuesChanged(List> previous, List> current) { TrackChanges(Targets.Owner, previous, current); } /// /// Keeps track of each client instsnce releative player instance with this component /// private void TrackRelativeInstances() { if (!Instances.ContainsKey(NetworkManager.LocalClientId)) { Instances.Add(NetworkManager.LocalClientId, new Dictionary()); } if (!Instances[NetworkManager.LocalClientId].ContainsKey(NetworkObjectId)) { Instances[NetworkManager.LocalClientId].Add(NetworkObjectId, this); } ResetTrackedChanges(); } public void ResetTrackedChanges() { NetworkVariableChanges.Clear(); NetworkVariableChanges.Add(Targets.Owner, new Dictionary>>()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.Added, new List>()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.Changed, new List>()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.Removed, new List>()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.UnChanged, new List>()); NetworkVariableChanges.Add(Targets.Server, new Dictionary>>()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.Added, new List>()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.Changed, new List>()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.Removed, new List>()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.UnChanged, new List>()); } protected override void OnNetworkPostSpawn() { TrackRelativeInstances(); ListCollectionServer.OnValueChanged += OnServerListValuesChanged; ListCollectionOwner.OnValueChanged += OnOwnerListValuesChanged; if (IsServer) { ListCollectionServer.Value = OnSetServerValues(); ListCollectionServer.CheckDirtyState(); } if (IsOwner) { ListCollectionOwner.Value = OnSetOwnerValues(); ListCollectionOwner.CheckDirtyState(); } base.OnNetworkPostSpawn(); } public override void OnNetworkDespawn() { ListCollectionServer.OnValueChanged -= OnServerListValuesChanged; ListCollectionOwner.OnValueChanged -= OnOwnerListValuesChanged; base.OnNetworkDespawn(); } } public class ListTestHelperInt : ListTestHelperBase, IListTestHelperBase { public static Dictionary> Instances = new Dictionary>(); public static void ResetState() { Instances.Clear(); } public NetworkVariable> ListCollectionServer = new NetworkVariable>(new List(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); public NetworkVariable> ListCollectionOwner = new NetworkVariable>(new List(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); // This tracks what has changed per instance which is used to compare to all other instances public Dictionary>> NetworkVariableChanges = new Dictionary>>(); public bool ValidateInstances() { foreach (var clientId in NetworkManager.ConnectedClientsIds) { if (clientId == NetworkManager.LocalClientId) { continue; } if (!Instances.ContainsKey(clientId)) { return false; } if (!Instances[clientId].ContainsKey(NetworkObjectId)) { return false; } var otherOwnerCollection = Instances[clientId][NetworkObjectId].ListCollectionOwner; var otherServerCollection = Instances[clientId][NetworkObjectId].ListCollectionServer; if (!ListCollectionOwner.Value.SequenceEqual(otherOwnerCollection.Value)) { return false; } if (!ListCollectionServer.Value.SequenceEqual(otherServerCollection.Value)) { return false; } } return true; } private bool ChangesMatch(Dictionary> local, Dictionary> other) { var deltaTypes = Enum.GetValues(typeof(DeltaTypes)).OfType().ToList(); foreach (var deltaType in deltaTypes) { LogMessage($"Comparing {deltaType}:"); if (local[deltaType].Count != other[deltaType].Count) { LogMessage($"{deltaType}s did not match!"); return false; } for (int i = 0; i < local[deltaType].Count; i++) { if (!local[deltaType][i].Equals(other[deltaType][i])) { LogMessage($"Sequence set ({i}) does not match! Local[{i}] = {local[deltaType][i]} Remote[{i}].Count = {other[deltaType][i]}."); return false; } } } return true; } public override bool CompareTrackedChanges(Targets target) { LogStart(); var localChanges = NetworkVariableChanges[target]; foreach (var clientId in NetworkManager.ConnectedClientsIds) { if (clientId == NetworkManager.LocalClientId) { continue; } if (!Instances.ContainsKey(clientId)) { return false; } if (!Instances[clientId].ContainsKey(NetworkObjectId)) { return false; } var entry = Instances[clientId][NetworkObjectId]; var otherChanges = entry.NetworkVariableChanges[target]; LogMessage($"Comparing against client-{clientId} {entry.name}:"); if (!ChangesMatch(localChanges, otherChanges)) { LogMessage($"Client-{clientId} {entry.name} did not match!"); return false; } LogMessage($"Client-{clientId} {entry.name} matched!"); } return true; } private List GetInitialValues() { var list = new List(); for (int i = 0; i < 10; i++) { list.Add(Random.Range(0, ushort.MaxValue)); } return list; } public NetworkVariable> GetNetVar(Targets target) { return target == Targets.Server ? ListCollectionServer : ListCollectionOwner; } public List OnSetServerValues() { return GetInitialValues(); } public List OnSetOwnerValues() { return GetInitialValues(); } public void UpdateValue(int value, Targets target, bool checkDirty = true) { var netVar = GetNetVar(target); var index = netVar.Value.IndexOf(value); netVar.Value[index] = value; if (checkDirty) { netVar.CheckDirtyState(); } } public void Add(int value, Targets target) { var netVar = GetNetVar(target); netVar.Value.Add(value); netVar.CheckDirtyState(); } public void AddRange(List values, Targets target) { var netVar = GetNetVar(target); netVar.Value.AddRange(values); netVar.CheckDirtyState(); } public void Insert(int value, int index, Targets target, bool checkDirty = true) { var netVar = GetNetVar(target); netVar.Value.Insert(index, value); if (checkDirty) { netVar.CheckDirtyState(); } } public void Remove(int value, Targets target) { var netVar = GetNetVar(target); netVar.Value.Remove(value); netVar.CheckDirtyState(); } public void FullSet(List values, Targets target) { var netVar = GetNetVar(target); netVar.Value = values; netVar.CheckDirtyState(); } public void Clear(Targets target) { var netVar = GetNetVar(target); netVar.Value.Clear(); netVar.CheckDirtyState(); } public void TrackChanges(Targets target, List previous, List current) { var contextTable = NetworkVariableChanges[target]; var whatWasAdded = current.Except(previous).ToList(); var whatWasRemoved = previous.Where((c) => !current.Contains(c)).ToList(); var whatWasNeitherAddedOrRemoved = current.Where((c) => previous.Contains(c) && !whatWasAdded.Contains(c)).ToList(); var whatChanged = whatWasNeitherAddedOrRemoved.Where((c) => previous.Contains(c) && !previous.Where((d) => d.Equals(c)).FirstOrDefault().Equals(c)).ToList(); var whatRemainedTheSame = whatWasNeitherAddedOrRemoved.Where((c) => !whatChanged.Contains(c)).ToList(); contextTable[DeltaTypes.Added] = whatWasAdded; contextTable[DeltaTypes.Removed] = whatWasRemoved; contextTable[DeltaTypes.Changed] = whatChanged; contextTable[DeltaTypes.UnChanged] = whatRemainedTheSame; } public void OnServerListValuesChanged(List previous, List current) { TrackChanges(Targets.Server, previous, current); } public void OnOwnerListValuesChanged(List previous, List current) { TrackChanges(Targets.Owner, previous, current); } /// /// Keeps track of each client instsnce releative player instance with this component /// private void TrackRelativeInstances() { if (!Instances.ContainsKey(NetworkManager.LocalClientId)) { Instances.Add(NetworkManager.LocalClientId, new Dictionary()); } if (!Instances[NetworkManager.LocalClientId].ContainsKey(NetworkObjectId)) { Instances[NetworkManager.LocalClientId].Add(NetworkObjectId, this); } ResetTrackedChanges(); } public void ResetTrackedChanges() { NetworkVariableChanges.Clear(); NetworkVariableChanges.Add(Targets.Owner, new Dictionary>()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.Added, new List()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.Changed, new List()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.Removed, new List()); NetworkVariableChanges[Targets.Owner].Add(DeltaTypes.UnChanged, new List()); NetworkVariableChanges.Add(Targets.Server, new Dictionary>()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.Added, new List()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.Changed, new List()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.Removed, new List()); NetworkVariableChanges[Targets.Server].Add(DeltaTypes.UnChanged, new List()); } protected override void OnNetworkPostSpawn() { TrackRelativeInstances(); ListCollectionServer.OnValueChanged += OnServerListValuesChanged; ListCollectionOwner.OnValueChanged += OnOwnerListValuesChanged; if (IsServer) { ListCollectionServer.Value = OnSetServerValues(); ListCollectionOwner.CheckDirtyState(); } if (IsOwner) { ListCollectionOwner.Value = OnSetOwnerValues(); ListCollectionOwner.CheckDirtyState(); } base.OnNetworkPostSpawn(); } public override void OnNetworkDespawn() { ListCollectionServer.OnValueChanged -= OnServerListValuesChanged; ListCollectionOwner.OnValueChanged -= OnOwnerListValuesChanged; base.OnNetworkDespawn(); } } #endregion #region BASE TEST COMPONENT HELPERS public class ListTestHelperBase : NetworkBehaviour { public enum Targets { Server, Owner } public enum DeltaTypes { Added, Removed, Changed, UnChanged } private StringBuilder m_StringBuilder = new StringBuilder(); public string GetLog() { return m_StringBuilder.ToString(); } protected void LogMessage(string message) { m_StringBuilder.AppendLine(message); } protected void LogStart() { m_StringBuilder.Clear(); m_StringBuilder.AppendLine($"[Client-{NetworkManager.LocalClientId}][{name}] Log Started."); } public virtual bool CompareTrackedChanges(Targets target) { return false; } } public interface IListTestHelperBase { public bool ValidateInstances(); public NetworkVariable> GetNetVar(ListTestHelperBase.Targets target); public List OnSetServerValues(); public List OnSetOwnerValues(); public void UpdateValue(T value, ListTestHelperBase.Targets target, bool checkDirty = true); public void Add(T value, ListTestHelperBase.Targets target); public void AddRange(List values, ListTestHelperBase.Targets target); public void Insert(T value, int index, ListTestHelperBase.Targets target, bool checkDirty = true); public void Remove(T value, ListTestHelperBase.Targets target); public void FullSet(List values, ListTestHelperBase.Targets target); public void Clear(ListTestHelperBase.Targets target); public void TrackChanges(ListTestHelperBase.Targets target, List previous, List current); public void OnServerListValuesChanged(List previous, List current); public void OnOwnerListValuesChanged(List previous, List current); public void ResetTrackedChanges(); } public interface IDictionaryTestHelperBase { public bool ValidateInstances(); public NetworkVariable> GetNetVar(ListTestHelperBase.Targets target); public Dictionary OnSetServerValues(); public Dictionary OnSetOwnerValues(); public bool UpdateValue((TKey, TValue) value, ListTestHelperBase.Targets target, bool checkDirty = true); public void Add((TKey, TValue) value, ListTestHelperBase.Targets target); public void Remove(TKey key, ListTestHelperBase.Targets target); public void FullSet(Dictionary values, ListTestHelperBase.Targets target); public void Clear(ListTestHelperBase.Targets target); public void TrackChanges(ListTestHelperBase.Targets target, Dictionary previous, Dictionary current); public void OnServerListValuesChanged(Dictionary previous, Dictionary current); public void OnOwnerListValuesChanged(Dictionary previous, Dictionary current); public void ResetTrackedChanges(); } public interface IHashSetTestHelperBase { public bool ValidateInstances(); public NetworkVariable> GetNetVar(ListTestHelperBase.Targets target); public HashSet OnSetServerValues(); public HashSet OnSetOwnerValues(); public void Add(T value, ListTestHelperBase.Targets target); public void Remove(T value, ListTestHelperBase.Targets target); public void Clear(ListTestHelperBase.Targets target); public void TrackChanges(ListTestHelperBase.Targets target, HashSet previous, HashSet current); public void OnServerListValuesChanged(HashSet previous, HashSet current); public void OnOwnerListValuesChanged(HashSet previous, HashSet current); public void ResetTrackedChanges(); } #endregion }