From c2664dfb7fbce9763e7c7d648830ebcc5884e5ad Mon Sep 17 00:00:00 2001 From: Unity Technologies <@unity> Date: Tue, 20 Aug 2024 00:00:00 +0000 Subject: [PATCH] com.unity.netcode.gameobjects@1.11.0 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). ## [1.11.0] - 2024-08-20 ### Added - Added `NetworkVariable.CheckDirtyState` that is to be used in tandem with collections in order to detect whether the collection or an item within the collection has changed. (#3005) ### Fixed - Fixed issue by adding null checks in `NetworkVariableBase.CanClientRead` and `NetworkVariableBase.CanClientWrite` methods to ensure safe access to `NetworkBehaviour`. (#3011) - Fixed issue using collections within `NetworkVariable` where the collection would not detect changes to items or nested items. (#3005) - Fixed issue where `List`, `Dictionary`, and `HashSet` collections would not uniquely duplicate nested collections. (#3005) - Fixed Issue where a state with dual triggers, inbound and outbound, could cause a false layer to layer state transition message to be sent to non-authority `NetworkAnimator` instances and cause a warning message to be logged. (#2999) - Fixed issue where `FixedStringSerializer` was using `NetworkVariableSerialization.AreEqual` to determine if two bytes were equal causes an exception to be thrown due to no byte serializer having been defined. (#2992) ### Changed - Changed permissions exception thrown in `NetworkList` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3005) - Changed permissions exception thrown in `NetworkVariable.Value` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3005) --- CHANGELOG.md | 21 + Components/NetworkAnimator.cs | 11 +- Runtime/Core/NetworkBehaviour.cs | 5 +- .../AnticipatedNetworkVariable.cs | 1 - .../Collections/NetworkList.cs | 18 +- Runtime/NetworkVariable/NetworkVariable.cs | 58 +- .../NetworkVariable/NetworkVariableBase.cs | 38 +- .../NetworkVariableSerialization.cs | 27 +- .../NetworkVariableCollectionsTests.cs | 2994 +++++++++++++++++ .../NetworkVariableCollectionsTests.cs.meta | 11 + Tests/Runtime/NetworkVariableTests.cs | 10 +- Tests/Runtime/OwnerPermissionTests.cs | 69 +- package.json | 10 +- 13 files changed, 3179 insertions(+), 94 deletions(-) create mode 100644 Tests/Runtime/NetworkVariableCollectionsTests.cs create mode 100644 Tests/Runtime/NetworkVariableCollectionsTests.cs.meta diff --git a/CHANGELOG.md b/CHANGELOG.md index 94fe445..dfea0a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). + +## [1.11.0] - 2024-08-20 + +### Added + +- Added `NetworkVariable.CheckDirtyState` that is to be used in tandem with collections in order to detect whether the collection or an item within the collection has changed. (#3005) + +### Fixed + +- Fixed issue by adding null checks in `NetworkVariableBase.CanClientRead` and `NetworkVariableBase.CanClientWrite` methods to ensure safe access to `NetworkBehaviour`. (#3011) +- Fixed issue using collections within `NetworkVariable` where the collection would not detect changes to items or nested items. (#3005) +- Fixed issue where `List`, `Dictionary`, and `HashSet` collections would not uniquely duplicate nested collections. (#3005) +- Fixed Issue where a state with dual triggers, inbound and outbound, could cause a false layer to layer state transition message to be sent to non-authority `NetworkAnimator` instances and cause a warning message to be logged. (#2999) +- Fixed issue where `FixedStringSerializer` was using `NetworkVariableSerialization.AreEqual` to determine if two bytes were equal causes an exception to be thrown due to no byte serializer having been defined. (#2992) + +### Changed + +- Changed permissions exception thrown in `NetworkList` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3005) +- Changed permissions exception thrown in `NetworkVariable.Value` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3005) + + ## [1.10.0] - 2024-07-22 ### Added diff --git a/Components/NetworkAnimator.cs b/Components/NetworkAnimator.cs index 8aaea1e..4eee6a0 100644 --- a/Components/NetworkAnimator.cs +++ b/Components/NetworkAnimator.cs @@ -832,7 +832,12 @@ private void CheckForStateChange(int layer) stateChangeDetected = true; //Debug.Log($"[Cross-Fade] To-Hash: {nt.fullPathHash} | TI-Duration: ({tt.duration}) | TI-Norm: ({tt.normalizedTime}) | From-Hash: ({m_AnimationHash[layer]}) | SI-FPHash: ({st.fullPathHash}) | SI-Norm: ({st.normalizedTime})"); } - else if (!tt.anyState && tt.fullPathHash != m_TransitionHash[layer]) + // If we are not transitioned into the "any state" and the animator transition isn't a full path hash (layer to layer) and our pre-built destination state to transition does not contain the + // current layer (i.e. transitioning into a state from another layer) =or= we do contain the layer and the layer contains state to transition to is contained within our pre-built destination + // state then we can handle this transition as a non-cross fade state transition between layers. + // Otherwise, if we don't enter into this then this is a "trigger transition to some state that is now being transitioned back to the Idle state via trigger" or "Dual Triggers" IDLE<-->State. + else if (!tt.anyState && tt.fullPathHash != m_TransitionHash[layer] && (!m_DestinationStateToTransitioninfo.ContainsKey(layer) || + (m_DestinationStateToTransitioninfo.ContainsKey(layer) && m_DestinationStateToTransitioninfo[layer].ContainsKey(nt.fullPathHash)))) { // first time in this transition for this layer m_TransitionHash[layer] = tt.fullPathHash; @@ -841,6 +846,10 @@ private void CheckForStateChange(int layer) animState.CrossFade = false; animState.Transition = true; animState.NormalizedTime = tt.normalizedTime; + if (m_DestinationStateToTransitioninfo.ContainsKey(layer) && m_DestinationStateToTransitioninfo[layer].ContainsKey(nt.fullPathHash)) + { + animState.DestinationStateHash = nt.fullPathHash; + } stateChangeDetected = true; //Debug.Log($"[Transition] TI-Duration: ({tt.duration}) | TI-Norm: ({tt.normalizedTime}) | From-Hash: ({m_AnimationHash[layer]}) |SI-FPHash: ({st.fullPathHash}) | SI-Norm: ({st.normalizedTime})"); } diff --git a/Runtime/Core/NetworkBehaviour.cs b/Runtime/Core/NetworkBehaviour.cs index 386599f..5e4ce50 100644 --- a/Runtime/Core/NetworkBehaviour.cs +++ b/Runtime/Core/NetworkBehaviour.cs @@ -758,7 +758,7 @@ internal void InternalOnNetworkDespawn() } /// - /// Gets called when the local client gains ownership of this object + /// Invoked on both the server and the local client of the owner when ownership is assigned. /// public virtual void OnGainedOwnership() { } @@ -786,7 +786,8 @@ internal void InternalOnOwnershipChanged(ulong previous, ulong current) } /// - /// Gets called when we loose ownership of this object + /// Invoked on the local client when it loses ownership of the associated . + /// This method is also invoked on the server when any client loses ownership. /// public virtual void OnLostOwnership() { } diff --git a/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs b/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs index eb8744f..2a08293 100644 --- a/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs +++ b/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs @@ -51,7 +51,6 @@ public enum StaleDataHandling #pragma warning restore IDE0001 [Serializable] [GenerateSerializationForGenericParameter(0)] - [GenerateSerializationForType(typeof(byte))] public class AnticipatedNetworkVariable : NetworkVariableBase { [SerializeField] diff --git a/Runtime/NetworkVariable/Collections/NetworkList.cs b/Runtime/NetworkVariable/Collections/NetworkList.cs index a082b4e..a489e9d 100644 --- a/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -369,7 +369,8 @@ public void Add(T item) // check write permissions if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) { - throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); + LogWritePermissionError(); + return; } m_List.Add(item); @@ -390,7 +391,8 @@ public void Clear() // check write permissions if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) { - throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); + LogWritePermissionError(); + return; } m_List.Clear(); @@ -416,7 +418,8 @@ public bool Remove(T item) // check write permissions if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) { - throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); + LogWritePermissionError(); + return false; } int index = m_List.IndexOf(item); @@ -451,7 +454,8 @@ public void Insert(int index, T item) // check write permissions if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) { - throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); + LogWritePermissionError(); + return; } if (index < m_List.Length) @@ -480,7 +484,8 @@ public void RemoveAt(int index) // check write permissions if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) { - throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); + LogWritePermissionError(); + return; } var value = m_List[index]; @@ -505,7 +510,8 @@ public T this[int index] // check write permissions if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) { - throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); + LogWritePermissionError(); + return; } var previousValue = m_List[index]; diff --git a/Runtime/NetworkVariable/NetworkVariable.cs b/Runtime/NetworkVariable/NetworkVariable.cs index 50d3098..938d534 100644 --- a/Runtime/NetworkVariable/NetworkVariable.cs +++ b/Runtime/NetworkVariable/NetworkVariable.cs @@ -9,7 +9,6 @@ namespace Unity.Netcode /// the unmanaged type for [Serializable] [GenerateSerializationForGenericParameter(0)] - [GenerateSerializationForType(typeof(byte))] public class NetworkVariable : NetworkVariableBase { /// @@ -80,25 +79,57 @@ public NetworkVariable(T value = default, /// /// The value of the NetworkVariable container /// + /// + /// When assigning collections to , unless it is a completely new collection this will not + /// detect any deltas with most managed collection classes since assignment of one collection value to another + /// is actually just a reference to the collection itself.
+ /// To detect deltas in a collection, you should invoke after making modifications to the collection. + ///
public virtual T Value { get => m_InternalValue; set { - // Compare bitwise - if (NetworkVariableSerialization.AreEqual(ref m_InternalValue, ref value)) + if (m_NetworkManager && !CanClientWrite(m_NetworkManager.LocalClientId)) { + LogWritePermissionError(); return; } - if (m_NetworkBehaviour && !CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) + // Compare the Value being applied to the current value + if (!NetworkVariableSerialization.AreEqual(ref m_InternalValue, ref value)) { - throw new InvalidOperationException("Client is not allowed to write to this NetworkVariable"); + T previousValue = m_InternalValue; + m_InternalValue = value; + SetDirty(true); + m_IsDisposed = false; + OnValueChanged?.Invoke(previousValue, m_InternalValue); } + } + } + + /// + /// Invoke this method to check if a collection's items are dirty. + /// The default behavior is to exit early if the is already dirty. + /// + /// when true, this check will force a full item collection check even if the NetworkVariable is already dirty + /// + /// This is to be used as a way to check if a containing a managed collection has any changees to the collection items.
+ /// If you invoked this when a collection is dirty, it will not trigger the unless you set to true.
+ ///
+ public bool CheckDirtyState(bool forceCheck = false) + { + var isDirty = base.IsDirty(); - Set(value); + // Compare the previous with the current if not dirty or forcing a check. + if ((!isDirty || forceCheck) && !NetworkVariableSerialization.AreEqual(ref m_PreviousValue, ref m_InternalValue)) + { + SetDirty(true); + OnValueChanged?.Invoke(m_PreviousValue, m_InternalValue); m_IsDisposed = false; + isDirty = true; } + return isDirty; } internal ref T RefValue() @@ -185,9 +216,8 @@ public override void ResetDirty() private protected void Set(T value) { SetDirty(true); - T previousValue = m_InternalValue; m_InternalValue = value; - OnValueChanged?.Invoke(previousValue, m_InternalValue); + OnValueChanged?.Invoke(m_PreviousValue, m_InternalValue); } /// @@ -206,20 +236,22 @@ public override void WriteDelta(FastBufferWriter writer) /// Whether or not the container should keep the dirty delta, or mark the delta as consumed public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) { + // In order to get managed collections to properly have a previous and current value, we have to + // duplicate the collection at this point before making any modifications to the current. + m_HasPreviousValue = true; + NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_PreviousValue); + NetworkVariableSerialization.ReadDelta(reader, ref m_InternalValue); + // todo: // keepDirtyDelta marks a variable received as dirty and causes the server to send the value to clients // In a prefect world, whether a variable was A) modified locally or B) received and needs retransmit // would be stored in different fields - - T previousValue = m_InternalValue; - NetworkVariableSerialization.ReadDelta(reader, ref m_InternalValue); - if (keepDirtyDelta) { SetDirty(true); } - OnValueChanged?.Invoke(previousValue, m_InternalValue); + OnValueChanged?.Invoke(m_PreviousValue, m_InternalValue); } /// diff --git a/Runtime/NetworkVariable/NetworkVariableBase.cs b/Runtime/NetworkVariable/NetworkVariableBase.cs index 22028dd..8c7db22 100644 --- a/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -32,21 +32,47 @@ public abstract class NetworkVariableBase : IDisposable /// Maintains a link to the associated NetworkBehaviour /// private protected NetworkBehaviour m_NetworkBehaviour; + private NetworkManager m_InternalNetworkManager; public NetworkBehaviour GetBehaviour() { return m_NetworkBehaviour; } + internal string GetWritePermissionError() + { + return $"|Client-{m_NetworkManager.LocalClientId}|{m_NetworkBehaviour.name}|{Name}| Write permissions ({WritePerm}) for this client instance is not allowed!"; + } + + internal void LogWritePermissionError() + { + Debug.LogError(GetWritePermissionError()); + } + + private protected NetworkManager m_NetworkManager + { + get + { + if (m_InternalNetworkManager == null && m_NetworkBehaviour && m_NetworkBehaviour.NetworkObject?.NetworkManager) + { + m_InternalNetworkManager = m_NetworkBehaviour.NetworkObject?.NetworkManager; + } + return m_InternalNetworkManager; + } + } + /// /// Initializes the NetworkVariable /// /// The NetworkBehaviour the NetworkVariable belongs to public void Initialize(NetworkBehaviour networkBehaviour) { + m_InternalNetworkManager = null; m_NetworkBehaviour = networkBehaviour; - if (m_NetworkBehaviour.NetworkManager) + if (m_NetworkBehaviour && m_NetworkBehaviour.NetworkObject?.NetworkManager) { + m_InternalNetworkManager = m_NetworkBehaviour.NetworkObject?.NetworkManager; + if (m_NetworkBehaviour.NetworkManager.NetworkTimeSystem != null) { UpdateLastSentTime(); @@ -217,6 +243,11 @@ public virtual bool IsDirty() /// Whether or not the client has permission to read public bool CanClientRead(ulong clientId) { + if (!m_NetworkBehaviour) + { + return false; + } + switch (ReadPerm) { default: @@ -234,6 +265,11 @@ public bool CanClientRead(ulong clientId) /// Whether or not the client has permission to write public bool CanClientWrite(ulong clientId) { + if (!m_NetworkBehaviour) + { + return false; + } + switch (WritePerm) { default: diff --git a/Runtime/NetworkVariable/NetworkVariableSerialization.cs b/Runtime/NetworkVariable/NetworkVariableSerialization.cs index 1f6d552..70c94cf 100644 --- a/Runtime/NetworkVariable/NetworkVariableSerialization.cs +++ b/Runtime/NetworkVariable/NetworkVariableSerialization.cs @@ -349,7 +349,10 @@ public void Duplicate(in List value, ref List duplicatedValue) duplicatedValue.Clear(); foreach (var item in value) { - duplicatedValue.Add(item); + // This handles the nested list scenario List> + T subValue = default; + NetworkVariableSerialization.Duplicate(item, ref subValue); + duplicatedValue.Add(subValue); } } } @@ -421,6 +424,9 @@ public void Duplicate(in HashSet value, ref HashSet duplicatedValue) duplicatedValue.Clear(); foreach (var item in value) { + // Handles nested HashSets + T subValue = default; + NetworkVariableSerialization.Duplicate(item, ref subValue); duplicatedValue.Add(item); } } @@ -497,7 +503,12 @@ public void Duplicate(in Dictionary value, ref Dictionary.Duplicate(item.Key, ref subKey); + NetworkVariableSerialization.Duplicate(item.Value, ref subValue); + duplicatedValue.Add(subKey, subValue); } } } @@ -698,7 +709,7 @@ public unsafe void WriteDelta(FastBufferWriter writer, ref T value, ref T previo { var val = value[i]; var prevVal = previousValue[i]; - if (!NetworkVariableSerialization.AreEqual(ref val, ref prevVal)) + if (val != prevVal) { ++numChanges; changes.Set(i); @@ -723,19 +734,11 @@ public unsafe void WriteDelta(FastBufferWriter writer, ref T value, ref T previo unsafe { byte* ptr = value.GetUnsafePtr(); - byte* prevPtr = previousValue.GetUnsafePtr(); for (int i = 0; i < value.Length; ++i) { if (changes.IsSet(i)) { - if (i < previousValue.Length) - { - NetworkVariableSerialization.WriteDelta(writer, ref ptr[i], ref prevPtr[i]); - } - else - { - NetworkVariableSerialization.Write(writer, ref ptr[i]); - } + writer.WriteByteSafe(ptr[i]); } } } diff --git a/Tests/Runtime/NetworkVariableCollectionsTests.cs b/Tests/Runtime/NetworkVariableCollectionsTests.cs new file mode 100644 index 0000000..7d7e785 --- /dev/null +++ b/Tests/Runtime/NetworkVariableCollectionsTests.cs @@ -0,0 +1,2994 @@ +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 +} diff --git a/Tests/Runtime/NetworkVariableCollectionsTests.cs.meta b/Tests/Runtime/NetworkVariableCollectionsTests.cs.meta new file mode 100644 index 0000000..8a0dfc0 --- /dev/null +++ b/Tests/Runtime/NetworkVariableCollectionsTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 939ac41f36685f84e94a4b66ebbb6d8c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/NetworkVariableTests.cs b/Tests/Runtime/NetworkVariableTests.cs index b3cef36..2029fa1 100644 --- a/Tests/Runtime/NetworkVariableTests.cs +++ b/Tests/Runtime/NetworkVariableTests.cs @@ -254,7 +254,8 @@ public IEnumerator ClientCannotChangeServerWritableNetVar() var oldValue = testCompClient.ServerWritable_Position.Value; var newValue = oldValue + new Vector3(Random.Range(0, 100.0f), Random.Range(0, 100.0f), Random.Range(0, 100.0f)); - Assert.That(() => testCompClient.ServerWritable_Position.Value = newValue, Throws.TypeOf()); + LogAssert.Expect(LogType.Error, testCompClient.ServerWritable_Position.GetWritePermissionError()); + testCompClient.ServerWritable_Position.Value = newValue; yield return WaitForPositionsAreEqual(testCompServer.ServerWritable_Position, oldValue); yield return WaitForServerWritableAreEqualOnAll(); @@ -283,7 +284,8 @@ public IEnumerator ServerCannotChangeOwnerWritableNetVar() var oldValue = testCompServer.OwnerWritable_Position.Value; var newValue = oldValue + new Vector3(Random.Range(0, 100.0f), Random.Range(0, 100.0f), Random.Range(0, 100.0f)); - Assert.That(() => testCompServer.OwnerWritable_Position.Value = newValue, Throws.TypeOf()); + LogAssert.Expect(LogType.Error, testCompServer.OwnerWritable_Position.GetWritePermissionError()); + testCompServer.OwnerWritable_Position.Value = newValue; yield return WaitForPositionsAreEqual(testCompServer.OwnerWritable_Position, oldValue); yield return WaitForOwnerWritableAreEqualOnAll(); @@ -589,8 +591,8 @@ public void ClientWritePermissionTest([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); - // client must not be allowed to write to a server auth variable - Assert.Throws(() => m_Player1OnClient1.TheScalar.Value = k_TestVal1); + LogAssert.Expect(LogType.Error, m_Player1OnClient1.TheScalar.GetWritePermissionError()); + m_Player1OnClient1.TheScalar.Value = k_TestVal1; } /// diff --git a/Tests/Runtime/OwnerPermissionTests.cs b/Tests/Runtime/OwnerPermissionTests.cs index 7bd2816..0d8f36e 100644 --- a/Tests/Runtime/OwnerPermissionTests.cs +++ b/Tests/Runtime/OwnerPermissionTests.cs @@ -1,4 +1,3 @@ -using System; using System.Collections; using System.Collections.Generic; using Unity.Netcode.TestHelpers.Runtime; @@ -130,72 +129,44 @@ public IEnumerator OwnerPermissionTest() for (var clientWriting = 0; clientWriting < 3; clientWriting++) { // ==== Server-writable NetworkVariable ==== - var gotException = false; - Debug.Log($"Writing to server-write variable on object {objectIndex} on client {clientWriting}"); + VerboseDebug($"Writing to server-write variable on object {objectIndex} on client {clientWriting}"); - try + nextValueToWrite++; + if (clientWriting != serverIndex) { - nextValueToWrite++; - OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkVariableServer.Value = nextValueToWrite; + LogAssert.Expect(LogType.Error, OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkVariableServer.GetWritePermissionError()); } - catch (Exception) - { - gotException = true; - } - - // Verify server-owned netvar can only be written by server - Debug.Assert(gotException == (clientWriting != serverIndex)); + OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkVariableServer.Value = nextValueToWrite; // ==== Owner-writable NetworkVariable ==== - gotException = false; - Debug.Log($"Writing to owner-write variable on object {objectIndex} on client {clientWriting}"); + VerboseDebug($"Writing to owner-write variable on object {objectIndex} on client {clientWriting}"); - try + nextValueToWrite++; + if (clientWriting != objectIndex) { - nextValueToWrite++; - OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkVariableOwner.Value = nextValueToWrite; + LogAssert.Expect(LogType.Error, OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkVariableOwner.GetWritePermissionError()); } - catch (Exception) - { - gotException = true; - } - - // Verify client-owned netvar can only be written by owner - Debug.Assert(gotException == (clientWriting != objectIndex)); + OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkVariableOwner.Value = nextValueToWrite; // ==== Server-writable NetworkList ==== - gotException = false; - Debug.Log($"Writing to server-write list on object {objectIndex} on client {clientWriting}"); + VerboseDebug($"Writing to [Add] server-write NetworkList on object {objectIndex} on client {clientWriting}"); - try + nextValueToWrite++; + if (clientWriting != serverIndex) { - nextValueToWrite++; - OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkListServer.Add(nextValueToWrite); + LogAssert.Expect(LogType.Error, OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkListServer.GetWritePermissionError()); } - catch (Exception) - { - gotException = true; - } - - // Verify server-owned networkList can only be written by server - Debug.Assert(gotException == (clientWriting != serverIndex)); + OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkListServer.Add(nextValueToWrite); // ==== Owner-writable NetworkList ==== - gotException = false; - Debug.Log($"Writing to owner-write list on object {objectIndex} on client {clientWriting}"); + VerboseDebug($"Writing to [Add] owner-write NetworkList on object {objectIndex} on client {clientWriting}"); - try + nextValueToWrite++; + if (clientWriting != objectIndex) { - nextValueToWrite++; - OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkListOwner.Add(nextValueToWrite); + LogAssert.Expect(LogType.Error, OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkListOwner.GetWritePermissionError()); } - catch (Exception) - { - gotException = true; - } - - // Verify client-owned networkList can only be written by owner - Debug.Assert(gotException == (clientWriting != objectIndex)); + OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkListOwner.Add(nextValueToWrite); yield return WaitForTicks(m_ServerNetworkManager, 5); yield return WaitForTicks(m_ClientNetworkManagers[0], 5); diff --git a/package.json b/package.json index fbba616..a67ca47 100644 --- a/package.json +++ b/package.json @@ -2,23 +2,23 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "1.10.0", + "version": "1.11.0", "unity": "2021.3", "dependencies": { "com.unity.nuget.mono-cecil": "1.10.1", "com.unity.transport": "1.4.0" }, "_upm": { - "changelog": "### Added\n\n- Added `NetworkBehaviour.OnNetworkPreSpawn` and `NetworkBehaviour.OnNetworkPostSpawn` methods that provide the ability to handle pre and post spawning actions during the `NetworkObject` spawn sequence. (#2906)\n- Added a client-side only `NetworkBehaviour.OnNetworkSessionSynchronized` convenience method that is invoked on all `NetworkBehaviour`s after a newly joined client has finished synchronizing with the network session in progress. (#2906)\n- Added `NetworkBehaviour.OnInSceneObjectsSpawned` convenience method that is invoked when all in-scene `NetworkObject`s have been spawned after a scene has been loaded or upon a host or server starting. (#2906)\n\n### Fixed\n\n- Fixed issue where the realtime network stats monitor was not able to display RPC traffic in release builds due to those stats being only available in development builds or the editor. (#2980)\n- Fixed issue where `NetworkManager.ScenesLoaded` was not being updated if `PostSynchronizationSceneUnloading` was set and any loaded scenes not used during synchronization were unloaded.(#2977)\n- Fixed issue where internal delta serialization could not have a byte serializer defined when serializing deltas for other types. Added `[GenerateSerializationForType(typeof(byte))]` to both the `NetworkVariable` and `AnticipatedNetworkVariable` classes to assure a byte serializer is defined. (#2953)\n- Fixed issue with the client count not being correct on the host or server side when a client disconnects itself from a session. (#2941)\n- Fixed issue with the host trying to send itself a message that it has connected when first starting up. (#2941)\n- Fixed issue where in-scene placed NetworkObjects could be destroyed if a client disconnects early and/or before approval. (#2923)\n- Fixed issue where `NetworkDeltaPosition` would \"jitter\" periodically if both unreliable delta state updates and half-floats were used together. (#2922)\n- Fixed issue where `NetworkRigidbody2D` would not properly change body type based on the instance's authority when spawned. (#2916)\n- Fixed issue where a `NetworkObject` component's associated `NetworkBehaviour` components would not be detected if scene loading is disabled in the editor and the currently loaded scene has in-scene placed `NetworkObject`s. (#2906)\n- Fixed issue where an in-scene placed `NetworkObject` with `NetworkTransform` that is also parented under a `GameObject` would not properly synchronize when the parent `GameObject` had a world space position other than 0,0,0. (#2895)\n\n### Changed" + "changelog": "### Added\n\n- Added `NetworkVariable.CheckDirtyState` that is to be used in tandem with collections in order to detect whether the collection or an item within the collection has changed. (#3005)\n\n### Fixed\n\n- Fixed issue by adding null checks in `NetworkVariableBase.CanClientRead` and `NetworkVariableBase.CanClientWrite` methods to ensure safe access to `NetworkBehaviour`. (#3011)\n- Fixed issue using collections within `NetworkVariable` where the collection would not detect changes to items or nested items. (#3005)\n- Fixed issue where `List`, `Dictionary`, and `HashSet` collections would not uniquely duplicate nested collections. (#3005)\n- Fixed Issue where a state with dual triggers, inbound and outbound, could cause a false layer to layer state transition message to be sent to non-authority `NetworkAnimator` instances and cause a warning message to be logged. (#2999)\n- Fixed issue where `FixedStringSerializer` was using `NetworkVariableSerialization.AreEqual` to determine if two bytes were equal causes an exception to be thrown due to no byte serializer having been defined. (#2992)\n\n### Changed\n\n- Changed permissions exception thrown in `NetworkList` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3005)\n- Changed permissions exception thrown in `NetworkVariable.Value` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3005)" }, "upmCi": { - "footprint": "f100032a819fdef7edacd4fdfa133b189f94ef7d" + "footprint": "aa624034952045f7f2399c1f99fd31b764234959" }, - "documentationUrl": "https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@1.10/manual/index.html", + "documentationUrl": "https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@1.11/manual/index.html", "repository": { "url": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git", "type": "git", - "revision": "a342c8ddce87ae46a30dffbebb8f87ac364c0a87" + "revision": "e3303ba66b4a642ccf0bc72104107e1b8e1ebe1c" }, "samples": [ {