diff --git a/CHANGELOG.md b/CHANGELOG.md index 81d9146..8bf4b11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,30 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). +## [2.0.0-pre.4] - 2024-08-21 + +### 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. (#3004) + +### Fixed + +- Fixed issue where nested `NetworkTransform` components were not getting updated. (#3016) +- Fixed issue by adding null checks in `NetworkVariableBase.CanClientRead` and `NetworkVariableBase.CanClientWrite` methods to ensure safe access to `NetworkBehaviour`. (#3012) +- 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. (#3009) +- 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. (#3008) +- Fixed issue using collections within `NetworkVariable` where the collection would not detect changes to items or nested items. (#3004) +- Fixed issue where `List`, `Dictionary`, and `HashSet` collections would not uniquely duplicate nested collections. (#3004) +- Fixed issue where `NotAuthorityTarget` would include the service observer in the list of targets to send the RPC to as opposed to excluding the service observer as it should. (#3000) +- Fixed issue where `ProxyRpcTargetGroup` could attempt to send a message if there were no targets to send to. (#3000) + +### Changed + +- Changed `NetworkAnimator` to automatically switch to owner authoritative mode when using a distributed authority network topology. (#3021) +- Changed permissions exception thrown in `NetworkList` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3004) +- Changed permissions exception thrown in `NetworkVariable.Value` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3004) + + ## [2.0.0-pre.3] - 2024-07-23 ### Added diff --git a/Runtime/Components/NetworkAnimator.cs b/Runtime/Components/NetworkAnimator.cs index a1e3ccd..e01f4e7 100644 --- a/Runtime/Components/NetworkAnimator.cs +++ b/Runtime/Components/NetworkAnimator.cs @@ -498,9 +498,13 @@ internal bool IsServerAuthoritative() /// /// Override this method and return false to switch to owner authoritative mode /// + /// + /// When using a distributed authority network topology, this will default to + /// owner authoritative. + /// protected virtual bool OnIsServerAuthoritative() { - return true; + return NetworkManager ? !NetworkManager.DistributedAuthorityMode : true; } // Animators only support up to 32 parameters @@ -851,7 +855,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; @@ -860,6 +869,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/Components/NetworkTransform.cs b/Runtime/Components/NetworkTransform.cs index 939fe49..0cf2b41 100644 --- a/Runtime/Components/NetworkTransform.cs +++ b/Runtime/Components/NetworkTransform.cs @@ -2960,7 +2960,10 @@ private void CleanUpOnDestroyOrDespawn() #else var forUpdate = true; #endif - NetworkManager?.NetworkTransformRegistration(this, forUpdate, false); + if (m_CachedNetworkObject != null) + { + NetworkManager?.NetworkTransformRegistration(m_CachedNetworkObject, forUpdate, false); + } DeregisterForTickUpdate(this); CanCommitToTransform = false; } @@ -3069,7 +3072,7 @@ private void InternalInitialization(bool isOwnershipChange = false) if (CanCommitToTransform) { // Make sure authority doesn't get added to updates (no need to do this on the authority side) - m_CachedNetworkManager.NetworkTransformRegistration(this, forUpdate, false); + m_CachedNetworkManager.NetworkTransformRegistration(NetworkObject, forUpdate, false); if (UseHalfFloatPrecision) { m_HalfPositionState = new NetworkDeltaPosition(currentPosition, m_CachedNetworkManager.ServerTime.Tick, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ)); @@ -3090,7 +3093,7 @@ private void InternalInitialization(bool isOwnershipChange = false) else { // Non-authority needs to be added to updates for interpolation and applying state purposes - m_CachedNetworkManager.NetworkTransformRegistration(this, forUpdate, true); + m_CachedNetworkManager.NetworkTransformRegistration(NetworkObject, forUpdate, true); // Remove this instance from the tick update DeregisterForTickUpdate(this); ResetInterpolatedStateToCurrentAuthoritativeState(); diff --git a/Runtime/Core/NetworkBehaviour.cs b/Runtime/Core/NetworkBehaviour.cs index c1be9fa..5e088c7 100644 --- a/Runtime/Core/NetworkBehaviour.cs +++ b/Runtime/Core/NetworkBehaviour.cs @@ -806,7 +806,8 @@ internal void InternalOnNetworkDespawn() } /// - /// Gets called when the local client gains ownership of this object. + /// In client-server contexts, this method is invoked on both the server and the local client of the owner when ownership is assigned. + /// In distributed authority contexts, this method is only invoked on the local client that has been assigned ownership of the associated . /// public virtual void OnGainedOwnership() { } @@ -834,7 +835,9 @@ internal void InternalOnOwnershipChanged(ulong previous, ulong current) } /// - /// Gets called when ownership of this object is lost. + /// In client-server contexts, this method is invoked on the local client when it loses ownership of the associated + /// and on the server when any client loses ownership. + /// In distributed authority contexts, this method is only invoked on the local client that has lost ownership of the associated . /// public virtual void OnLostOwnership() { } @@ -1138,7 +1141,7 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie // Distributed Authority: All clients have read permissions, always try to write the value. if (NetworkVariableFields[j].CanClientRead(targetClientId)) { - // Write additional NetworkVariable information when length safety is enabled or when in distributed authority mode + // Write additional NetworkVariable information when length safety is enabled or when in distributed authority mode if (ensureLengthSafety || distributedAuthority) { // Write the type being serialized for distributed authority (only for comb-server) diff --git a/Runtime/Core/NetworkManager.cs b/Runtime/Core/NetworkManager.cs index ac38fa9..b5a042c 100644 --- a/Runtime/Core/NetworkManager.cs +++ b/Runtime/Core/NetworkManager.cs @@ -8,7 +8,6 @@ #endif using UnityEngine.SceneManagement; using Debug = UnityEngine.Debug; -using Unity.Netcode.Components; namespace Unity.Netcode { @@ -215,25 +214,25 @@ internal void PromoteSessionOwner(ulong clientId) } } - internal Dictionary NetworkTransformUpdate = new Dictionary(); + internal Dictionary NetworkTransformUpdate = new Dictionary(); #if COM_UNITY_MODULES_PHYSICS - internal Dictionary NetworkTransformFixedUpdate = new Dictionary(); + internal Dictionary NetworkTransformFixedUpdate = new Dictionary(); #endif - internal void NetworkTransformRegistration(NetworkTransform networkTransform, bool forUpdate = true, bool register = true) + internal void NetworkTransformRegistration(NetworkObject networkObject, bool onUpdate = true, bool register = true) { - if (forUpdate) + if (onUpdate) { if (register) { - if (!NetworkTransformUpdate.ContainsKey(networkTransform.NetworkObjectId)) + if (!NetworkTransformUpdate.ContainsKey(networkObject.NetworkObjectId)) { - NetworkTransformUpdate.Add(networkTransform.NetworkObjectId, networkTransform); + NetworkTransformUpdate.Add(networkObject.NetworkObjectId, networkObject); } } else { - NetworkTransformUpdate.Remove(networkTransform.NetworkObjectId); + NetworkTransformUpdate.Remove(networkObject.NetworkObjectId); } } #if COM_UNITY_MODULES_PHYSICS @@ -241,14 +240,14 @@ internal void NetworkTransformRegistration(NetworkTransform networkTransform, bo { if (register) { - if (!NetworkTransformFixedUpdate.ContainsKey(networkTransform.NetworkObjectId)) + if (!NetworkTransformFixedUpdate.ContainsKey(networkObject.NetworkObjectId)) { - NetworkTransformFixedUpdate.Add(networkTransform.NetworkObjectId, networkTransform); + NetworkTransformFixedUpdate.Add(networkObject.NetworkObjectId, networkObject); } } else { - NetworkTransformFixedUpdate.Remove(networkTransform.NetworkObjectId); + NetworkTransformFixedUpdate.Remove(networkObject.NetworkObjectId); } } #endif @@ -289,11 +288,21 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) #if COM_UNITY_MODULES_PHYSICS case NetworkUpdateStage.FixedUpdate: { - foreach (var networkTransformEntry in NetworkTransformFixedUpdate) + foreach (var networkObjectEntry in NetworkTransformFixedUpdate) { - if (networkTransformEntry.Value.gameObject.activeInHierarchy && networkTransformEntry.Value.IsSpawned) + // if not active or not spawned then skip + if (!networkObjectEntry.Value.gameObject.activeInHierarchy || !networkObjectEntry.Value.IsSpawned) { - networkTransformEntry.Value.OnFixedUpdate(); + continue; + } + + foreach (var networkTransformEntry in networkObjectEntry.Value.NetworkTransforms) + { + // only update if enabled + if (networkTransformEntry.enabled) + { + networkTransformEntry.OnFixedUpdate(); + } } } } @@ -308,11 +317,21 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) case NetworkUpdateStage.PreLateUpdate: { // Non-physics based non-authority NetworkTransforms update their states after all other components - foreach (var networkTransformEntry in NetworkTransformUpdate) + foreach (var networkObjectEntry in NetworkTransformUpdate) { - if (networkTransformEntry.Value.gameObject.activeInHierarchy && networkTransformEntry.Value.IsSpawned) + // if not active or not spawned then skip + if (!networkObjectEntry.Value.gameObject.activeInHierarchy || !networkObjectEntry.Value.IsSpawned) { - networkTransformEntry.Value.OnUpdate(); + continue; + } + + foreach (var networkTransformEntry in networkObjectEntry.Value.NetworkTransforms) + { + // only update if enabled + if (networkTransformEntry.enabled) + { + networkTransformEntry.OnUpdate(); + } } } } diff --git a/Runtime/Core/NetworkObject.cs b/Runtime/Core/NetworkObject.cs index 39d2262..884ea74 100644 --- a/Runtime/Core/NetworkObject.cs +++ b/Runtime/Core/NetworkObject.cs @@ -2359,7 +2359,7 @@ internal List ChildNetworkBehaviours { m_ChildNetworkBehaviours.Add(networkBehaviours[i]); var type = networkBehaviours[i].GetType(); - if (type.IsInstanceOfType(typeof(NetworkTransform)) || type.IsSubclassOf(typeof(NetworkTransform))) + if (type == typeof(NetworkTransform) || type.IsInstanceOfType(typeof(NetworkTransform)) || type.IsSubclassOf(typeof(NetworkTransform))) { if (NetworkTransforms == null) { diff --git a/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs b/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs index de73302..eb6050e 100644 --- a/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs +++ b/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs @@ -25,8 +25,6 @@ internal struct NetworkVariableDeltaMessage : INetworkMessage private const string k_Name = "NetworkVariableDeltaMessage"; - // DANGO-TODO: Made some modifications here that overlap/won't play nice with EnsureNetworkVariableLenghtSafety. - // Worth either merging or more cleanly separating these codepaths. public void Serialize(FastBufferWriter writer, int targetVersion) { if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex))) @@ -126,10 +124,6 @@ public void Serialize(FastBufferWriter writer, int targetVersion) } else { - // DANGO-TODO: - // Complex types with custom type serialization (either registered custom types or INetworkSerializable implementations) will be problematic - // Non-complex types always provide a full state update per delta - // DANGO-TODO: Add NetworkListEvent.EventType awareness to the cloud-state server if (networkManager.DistributedAuthorityMode) { var size_marker = writer.Position; @@ -167,8 +161,6 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int return true; } - // DANGO-TODO: Made some modifications here that overlap/won't play nice with EnsureNetworkVariableLenghtSafety. - // Worth either merging or more cleanly separating these codepaths. public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; diff --git a/Runtime/Messaging/RpcTargets/NotAuthorityRpcTarget.cs b/Runtime/Messaging/RpcTargets/NotAuthorityRpcTarget.cs index 5001f72..8ee736d 100644 --- a/Runtime/Messaging/RpcTargets/NotAuthorityRpcTarget.cs +++ b/Runtime/Messaging/RpcTargets/NotAuthorityRpcTarget.cs @@ -29,6 +29,12 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, { continue; } + + // The CMB-Service holds ID 0 and should not be added to the targets + if (clientId == NetworkManager.ServerClientId && m_NetworkManager.CMBServiceConnection) + { + continue; + } m_GroupSendTarget.Add(clientId); } } @@ -41,6 +47,12 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, continue; } + // The CMB-Service holds ID 0 and should not be added to the targets + if (clientId == NetworkManager.ServerClientId && m_NetworkManager.CMBServiceConnection) + { + continue; + } + if (clientId == m_NetworkManager.LocalClientId) { m_LocalSendRpcTarget.Send(behaviour, ref message, delivery, rpcParams); diff --git a/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs b/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs index 15bb63d..5c58b4f 100644 --- a/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs +++ b/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs @@ -17,6 +17,11 @@ internal class ProxyRpcTargetGroup : BaseRpcTarget, IDisposable, IGroupRpcTarget internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) { + // If there are no targets then don't attempt to send anything. + if (TargetClientIds.Length == 0 && Ids.Count == 0) + { + return; + } var proxyMessage = new ProxyMessage { Delivery = delivery, TargetClientIds = TargetClientIds.AsArray(), WrappedMessage = message }; #if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE var size = 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 7a79749..130cf4e 100644 --- a/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -393,7 +393,8 @@ public void Add(T item) // check write permissions if (!CanClientWrite(m_NetworkManager.LocalClientId)) { - throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); + LogWritePermissionError(); + return; } m_List.Add(item); @@ -414,7 +415,8 @@ public void Clear() // check write permissions if (!CanClientWrite(m_NetworkManager.LocalClientId)) { - throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); + LogWritePermissionError(); + return; } m_List.Clear(); @@ -440,7 +442,8 @@ public bool Remove(T item) // check write permissions if (!CanClientWrite(m_NetworkManager.LocalClientId)) { - throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); + LogWritePermissionError(); + return false; } int index = m_List.IndexOf(item); @@ -475,7 +478,8 @@ public void Insert(int index, T item) // check write permissions if (!CanClientWrite(m_NetworkManager.LocalClientId)) { - throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); + LogWritePermissionError(); + return; } if (index < m_List.Length) @@ -520,6 +524,8 @@ public void RemoveAt(int index) HandleAddListEvent(listEvent); } + + /// public T this[int index] { @@ -529,7 +535,8 @@ public T this[int index] // check write permissions if (!CanClientWrite(m_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 0bb39fb..a98197d 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 { /// @@ -95,25 +94,57 @@ public void Reset(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_NetworkManager && !CanClientWrite(m_NetworkManager.LocalClientId)) + // Compare the Value being applied to the current value + if (!NetworkVariableSerialization.AreEqual(ref m_InternalValue, ref value)) { - throw new InvalidOperationException($"[Client-{m_NetworkManager.LocalClientId}][{m_NetworkBehaviour.name}][{Name}] Write permissions ({WritePerm}) for this client instance is not allowed!"); + 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() @@ -194,19 +225,6 @@ public override void ResetDirty() base.ResetDirty(); } - /// - /// Sets the , marks the dirty, and invokes the callback - /// if there are subscribers to that event. - /// - /// the new value of type `T` to be set/> - private protected void Set(T value) - { - SetDirty(true); - T previousValue = m_InternalValue; - m_InternalValue = value; - OnValueChanged?.Invoke(previousValue, m_InternalValue); - } - /// /// Writes the variable to the writer /// @@ -223,20 +241,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 3431c50..a048cea 100644 --- a/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -37,6 +37,16 @@ public abstract class NetworkVariableBase : IDisposable internal virtual NetworkVariableType Type => NetworkVariableType.Unknown; + 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 @@ -254,6 +264,11 @@ public virtual bool IsDirty() /// Whether or not the client has permission to read public bool CanClientRead(ulong clientId) { + if (!m_NetworkBehaviour) + { + return false; + } + // When in distributed authority mode, everyone can read (but only the owner can write) if (m_NetworkManager != null && m_NetworkManager.DistributedAuthorityMode) { @@ -276,6 +291,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/Serialization/TypedSerializerImplementations.cs b/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs index 180f9e9..9f5a104 100644 --- a/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs +++ b/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs @@ -458,7 +458,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); } } } @@ -548,6 +551,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); } } @@ -641,7 +647,12 @@ public void Duplicate(in Dictionary value, ref Dictionary.Duplicate(item.Key, ref subKey); + NetworkVariableSerialization.Duplicate(item.Value, ref subValue); + duplicatedValue.Add(subKey, subValue); } } } @@ -924,7 +935,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); @@ -949,19 +960,11 @@ public unsafe void WriteDelta(FastBufferWriter writer, ref T value, ref T previo BytePacker.WriteValuePacked(writer, value.Length); writer.WriteValueSafe(changes); var ptr = value.GetUnsafePtr(); - var prevPtr = previousValue.GetUnsafePtr(); for (var 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/DistributedAuthority/DistributedAuthorityCodecTests.cs b/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs index 42934e1..397d4f0 100644 --- a/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs +++ b/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs @@ -40,11 +40,6 @@ internal class TestNetworkComponent : NetworkBehaviour public NetworkList MyNetworkList = new NetworkList(new List { 1, 2, 3 }); public NetworkVariable MyNetworkVar = new NetworkVariable(3); - [Rpc(SendTo.NotAuthority)] - public void TestNotAuthorityRpc(byte[] _) - { - } - [Rpc(SendTo.Authority)] public void TestAuthorityRpc(byte[] _) { @@ -263,15 +258,6 @@ public IEnumerator NetworkListDelta_WithValueUpdate() yield return m_ClientCodecHook.WaitForMessageReceived(); } - [UnityTest] - public IEnumerator NotAuthorityRpc() - { - Client.LocalClient.PlayerObject.GetComponent().TestNotAuthorityRpc(new byte[] { 1, 2, 3, 4 }); - - // Universal Rpcs are sent as a ProxyMessage (which contains an RpcMessage) - yield return m_ClientCodecHook.WaitForMessageReceived(); - } - [UnityTest] public IEnumerator ParentSync() { diff --git a/Tests/Runtime/DistributedAuthority/RpcProxyMessageTesting.cs b/Tests/Runtime/DistributedAuthority/RpcProxyMessageTesting.cs new file mode 100644 index 0000000..9a7a9f8 --- /dev/null +++ b/Tests/Runtime/DistributedAuthority/RpcProxyMessageTesting.cs @@ -0,0 +1,97 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; + +namespace Unity.Netcode.RuntimeTests +{ + /// + /// This test validates PR-3000 where it would invoke + /// TODO: + /// We really need to get the service running during tests + /// so we can validate these issues. While this test does + /// partially validate it we still need to manually validate + /// with a service connection. + /// + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.DAHost)] + public class RpcProxyMessageTesting : NetcodeIntegrationTest + { + protected override int NumberOfClients => 2; + + private List m_ProxyTestInstances = new List(); + + private StringBuilder m_ValidationLogger = new StringBuilder(); + + public RpcProxyMessageTesting(HostOrServer hostOrServer) : base(hostOrServer) { } + + protected override IEnumerator OnSetup() + { + m_ProxyTestInstances.Clear(); + return base.OnSetup(); + } + + protected override void OnCreatePlayerPrefab() + { + m_PlayerPrefab.AddComponent(); + base.OnCreatePlayerPrefab(); + } + + + private bool ValidateRpcProxyRpcs() + { + m_ValidationLogger.Clear(); + foreach (var proxy in m_ProxyTestInstances) + { + if (proxy.ReceivedRpc.Count < NumberOfClients) + { + m_ValidationLogger.AppendLine($"Not all clients received RPC from Client-{proxy.OwnerClientId}!"); + } + foreach (var clientId in proxy.ReceivedRpc) + { + if (clientId == proxy.OwnerClientId) + { + m_ValidationLogger.AppendLine($"Client-{proxy.OwnerClientId} sent itself an Rpc!"); + } + } + } + return m_ValidationLogger.Length == 0; + } + + + public IEnumerator ProxyDoesNotInvokeOnSender() + { + m_ProxyTestInstances.Add(m_ServerNetworkManager.LocalClient.PlayerObject.GetComponent()); + foreach (var client in m_ClientNetworkManagers) + { + m_ProxyTestInstances.Add(client.LocalClient.PlayerObject.GetComponent()); + } + + foreach (var clientProxyTest in m_ProxyTestInstances) + { + clientProxyTest.SendToEveryOneButMe(); + } + + yield return WaitForConditionOrTimeOut(ValidateRpcProxyRpcs); + AssertOnTimeout(m_ValidationLogger.ToString()); + } + + public class RpcProxyText : NetworkBehaviour + { + public List ReceivedRpc = new List(); + + public void SendToEveryOneButMe() + { + var baseTarget = NetworkManager.DistributedAuthorityMode ? RpcTarget.NotAuthority : RpcTarget.NotMe; + TestRpc(baseTarget); + } + + [Rpc(SendTo.SpecifiedInParams)] + private void TestRpc(RpcParams rpcParams = default) + { + ReceivedRpc.Add(rpcParams.Receive.SenderClientId); + } + } + } +} diff --git a/Tests/Runtime/DistributedAuthority/RpcProxyMessageTesting.cs.meta b/Tests/Runtime/DistributedAuthority/RpcProxyMessageTesting.cs.meta new file mode 100644 index 0000000..a877e82 --- /dev/null +++ b/Tests/Runtime/DistributedAuthority/RpcProxyMessageTesting.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 22d1d751fe245f7419f8393090c27106 \ No newline at end of file diff --git a/Tests/Runtime/NetworkVariable.meta b/Tests/Runtime/NetworkVariable.meta new file mode 100644 index 0000000..8dbf0bc --- /dev/null +++ b/Tests/Runtime/NetworkVariable.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7b4da27c6efa9684893f85f5d3ad80e6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/NetworkVarBufferCopyTest.cs b/Tests/Runtime/NetworkVariable/NetworkVarBufferCopyTest.cs similarity index 100% rename from Tests/Runtime/NetworkVarBufferCopyTest.cs rename to Tests/Runtime/NetworkVariable/NetworkVarBufferCopyTest.cs diff --git a/Tests/Runtime/NetworkVarBufferCopyTest.cs.meta b/Tests/Runtime/NetworkVariable/NetworkVarBufferCopyTest.cs.meta similarity index 100% rename from Tests/Runtime/NetworkVarBufferCopyTest.cs.meta rename to Tests/Runtime/NetworkVariable/NetworkVarBufferCopyTest.cs.meta diff --git a/Tests/Runtime/NetworkVariableAnticipationTests.cs b/Tests/Runtime/NetworkVariable/NetworkVariableAnticipationTests.cs similarity index 100% rename from Tests/Runtime/NetworkVariableAnticipationTests.cs rename to Tests/Runtime/NetworkVariable/NetworkVariableAnticipationTests.cs diff --git a/Tests/Runtime/NetworkVariableAnticipationTests.cs.meta b/Tests/Runtime/NetworkVariable/NetworkVariableAnticipationTests.cs.meta similarity index 100% rename from Tests/Runtime/NetworkVariableAnticipationTests.cs.meta rename to Tests/Runtime/NetworkVariable/NetworkVariableAnticipationTests.cs.meta diff --git a/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs b/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs new file mode 100644 index 0000000..7d7e785 --- /dev/null +++ b/Tests/Runtime/NetworkVariable/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/NetworkVariable/NetworkVariableCollectionsTests.cs.meta b/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs.meta new file mode 100644 index 0000000..32c6910 --- /dev/null +++ b/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7d01969a8bbbd7146a24490a5190ea7e \ No newline at end of file diff --git a/Tests/Runtime/NetworkVariable/NetworkVariableInheritanceTests.cs b/Tests/Runtime/NetworkVariable/NetworkVariableInheritanceTests.cs new file mode 100644 index 0000000..1a3a6d3 --- /dev/null +++ b/Tests/Runtime/NetworkVariable/NetworkVariableInheritanceTests.cs @@ -0,0 +1,165 @@ +#if !NGO_MINIMALPROJECT +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixtureSource(nameof(TestDataSource))] + internal class NetworkVariableInheritanceTests : NetcodeIntegrationTest + { + public NetworkVariableInheritanceTests(HostOrServer hostOrServer) + : base(hostOrServer) + { + } + + protected override int NumberOfClients => 2; + + public static IEnumerable TestDataSource() => + Enum.GetValues(typeof(HostOrServer)).OfType().Select(x => new TestFixtureData(x)); + + internal class ComponentA : NetworkBehaviour + { + public NetworkVariable PublicFieldA = new NetworkVariable(1); + protected NetworkVariable m_ProtectedFieldA = new NetworkVariable(2); + private NetworkVariable m_PrivateFieldA = new NetworkVariable(3); + + public void ChangeValuesA(int pub, int pro, int pri) + { + PublicFieldA.Value = pub; + m_ProtectedFieldA.Value = pro; + m_PrivateFieldA.Value = pri; + } + + public bool CompareValuesA(ComponentA other) + { + return PublicFieldA.Value == other.PublicFieldA.Value && + m_ProtectedFieldA.Value == other.m_ProtectedFieldA.Value && + m_PrivateFieldA.Value == other.m_PrivateFieldA.Value; + } + } + + internal class ComponentB : ComponentA + { + public NetworkVariable PublicFieldB = new NetworkVariable(11); + protected NetworkVariable m_ProtectedFieldB = new NetworkVariable(22); + private NetworkVariable m_PrivateFieldB = new NetworkVariable(33); + + public void ChangeValuesB(int pub, int pro, int pri) + { + PublicFieldB.Value = pub; + m_ProtectedFieldB.Value = pro; + m_PrivateFieldB.Value = pri; + } + + public bool CompareValuesB(ComponentB other) + { + return PublicFieldB.Value == other.PublicFieldB.Value && + m_ProtectedFieldB.Value == other.m_ProtectedFieldB.Value && + m_PrivateFieldB.Value == other.m_PrivateFieldB.Value; + } + } + + internal class ComponentC : ComponentB + { + public NetworkVariable PublicFieldC = new NetworkVariable(111); + protected NetworkVariable m_ProtectedFieldC = new NetworkVariable(222); + private NetworkVariable m_PrivateFieldC = new NetworkVariable(333); + + public void ChangeValuesC(int pub, int pro, int pri) + { + PublicFieldC.Value = pub; + m_ProtectedFieldA.Value = pro; + m_PrivateFieldC.Value = pri; + } + + public bool CompareValuesC(ComponentC other) + { + return PublicFieldC.Value == other.PublicFieldC.Value && + m_ProtectedFieldC.Value == other.m_ProtectedFieldC.Value && + m_PrivateFieldC.Value == other.m_PrivateFieldC.Value; + } + } + + private GameObject m_TestObjectPrefab; + private ulong m_TestObjectId = 0; + + protected override void OnOneTimeSetup() + { + NetworkVariableBase.IgnoreInitializeWarning = true; + base.OnOneTimeSetup(); + } + + protected override void OnOneTimeTearDown() + { + NetworkVariableBase.IgnoreInitializeWarning = false; + base.OnOneTimeTearDown(); + } + + protected override void OnServerAndClientsCreated() + { + m_TestObjectPrefab = CreateNetworkObjectPrefab($"[{nameof(NetworkVariableInheritanceTests)}.{nameof(m_TestObjectPrefab)}]"); + m_TestObjectPrefab.AddComponent(); + m_TestObjectPrefab.AddComponent(); + m_TestObjectPrefab.AddComponent(); + } + + protected override IEnumerator OnServerAndClientsConnected() + { + var serverTestObject = SpawnObject(m_TestObjectPrefab, m_ServerNetworkManager).GetComponent(); + m_TestObjectId = serverTestObject.NetworkObjectId; + + var serverTestComponentA = serverTestObject.GetComponent(); + var serverTestComponentB = serverTestObject.GetComponent(); + var serverTestComponentC = serverTestObject.GetComponent(); + + serverTestComponentA.ChangeValuesA(1000, 2000, 3000); + serverTestComponentB.ChangeValuesA(1000, 2000, 3000); + serverTestComponentB.ChangeValuesB(1100, 2200, 3300); + serverTestComponentC.ChangeValuesA(1000, 2000, 3000); + serverTestComponentC.ChangeValuesB(1100, 2200, 3300); + serverTestComponentC.ChangeValuesC(1110, 2220, 3330); + + yield return WaitForTicks(m_ServerNetworkManager, 2); + } + + private bool CheckTestObjectComponentValuesOnAll() + { + var serverTestObject = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjectId]; + var serverTestComponentA = serverTestObject.GetComponent(); + var serverTestComponentB = serverTestObject.GetComponent(); + var serverTestComponentC = serverTestObject.GetComponent(); + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + var clientTestObject = clientNetworkManager.SpawnManager.SpawnedObjects[m_TestObjectId]; + var clientTestComponentA = clientTestObject.GetComponent(); + var clientTestComponentB = clientTestObject.GetComponent(); + var clientTestComponentC = clientTestObject.GetComponent(); + if (!serverTestComponentA.CompareValuesA(clientTestComponentA) || + !serverTestComponentB.CompareValuesA(clientTestComponentB) || + !serverTestComponentB.CompareValuesB(clientTestComponentB) || + !serverTestComponentC.CompareValuesA(clientTestComponentC) || + !serverTestComponentC.CompareValuesB(clientTestComponentC) || + !serverTestComponentC.CompareValuesC(clientTestComponentC)) + { + return false; + } + } + + return true; + } + + [UnityTest] + public IEnumerator TestInheritedFields() + { + yield return WaitForConditionOrTimeOut(CheckTestObjectComponentValuesOnAll); + Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, nameof(CheckTestObjectComponentValuesOnAll)); + } + } +} +#endif diff --git a/Tests/Runtime/NetworkVariable/NetworkVariableInheritanceTests.cs.meta b/Tests/Runtime/NetworkVariable/NetworkVariableInheritanceTests.cs.meta new file mode 100644 index 0000000..3102d68 --- /dev/null +++ b/Tests/Runtime/NetworkVariable/NetworkVariableInheritanceTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 41d4aef8f33a8eb4e87879075f868e66 \ No newline at end of file diff --git a/Tests/Runtime/NetworkVariable/NetworkVariablePermissionTests.cs b/Tests/Runtime/NetworkVariable/NetworkVariablePermissionTests.cs new file mode 100644 index 0000000..73481fd --- /dev/null +++ b/Tests/Runtime/NetworkVariable/NetworkVariablePermissionTests.cs @@ -0,0 +1,306 @@ +#if !NGO_MINIMALPROJECT +using System; +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; +using Random = UnityEngine.Random; + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixtureSource(nameof(TestDataSource))] + internal class NetworkVariablePermissionTests : NetcodeIntegrationTest + { + public static IEnumerable TestDataSource() + { + NetworkVariableBase.IgnoreInitializeWarning = true; + foreach (HostOrServer hostOrServer in Enum.GetValues(typeof(HostOrServer))) + { + // DANGO-EXP TODO: Add support for distributed authority mode + if (hostOrServer == HostOrServer.DAHost) + { + continue; + } + yield return new TestFixtureData(hostOrServer); + } + + NetworkVariableBase.IgnoreInitializeWarning = false; + } + + protected override int NumberOfClients => 3; + + public NetworkVariablePermissionTests(HostOrServer hostOrServer) : base(hostOrServer) { } + + private GameObject m_TestObjPrefab; + private ulong m_TestObjId = 0; + + protected override void OnServerAndClientsCreated() + { + m_TestObjPrefab = CreateNetworkObjectPrefab($"[{nameof(NetworkVariablePermissionTests)}.{nameof(m_TestObjPrefab)}]"); + var testComp = m_TestObjPrefab.AddComponent(); + } + + protected override IEnumerator OnServerAndClientsConnected() + { + m_TestObjId = SpawnObject(m_TestObjPrefab, m_ServerNetworkManager).GetComponent().NetworkObjectId; + yield return null; + } + + private IEnumerator WaitForPositionsAreEqual(NetworkVariable netvar, Vector3 expected) + { + yield return WaitForConditionOrTimeOut(() => netvar.Value == expected); + Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut); + } + + private IEnumerator WaitForOwnerWritableAreEqualOnAll() + { + yield return WaitForConditionOrTimeOut(CheckOwnerWritableAreEqualOnAll); + Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut); + } + + private bool CheckOwnerWritableAreEqualOnAll() + { + var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; + var testCompServer = testObjServer.GetComponent(); + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + var testObjClient = clientNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; + var testCompClient = testObjClient.GetComponent(); + if (testObjServer.OwnerClientId != testObjClient.OwnerClientId || + testCompServer.OwnerWritable_Position.Value != testCompClient.OwnerWritable_Position.Value || + testCompServer.OwnerWritable_Position.ReadPerm != testCompClient.OwnerWritable_Position.ReadPerm || + testCompServer.OwnerWritable_Position.WritePerm != testCompClient.OwnerWritable_Position.WritePerm) + { + return false; + } + } + return true; + } + + private IEnumerator WaitForServerWritableAreEqualOnAll() + { + yield return WaitForConditionOrTimeOut(CheckServerWritableAreEqualOnAll); + Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut); + } + + private bool CheckServerWritableAreEqualOnAll() + { + var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; + var testCompServer = testObjServer.GetComponent(); + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + var testObjClient = clientNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; + var testCompClient = testObjClient.GetComponent(); + if (testCompServer.ServerWritable_Position.Value != testCompClient.ServerWritable_Position.Value || + testCompServer.ServerWritable_Position.ReadPerm != testCompClient.ServerWritable_Position.ReadPerm || + testCompServer.ServerWritable_Position.WritePerm != testCompClient.ServerWritable_Position.WritePerm) + { + return false; + } + } + return true; + } + + private bool CheckOwnerReadWriteAreEqualOnOwnerAndServer() + { + var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; + var testCompServer = testObjServer.GetComponent(); + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + var testObjClient = clientNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; + var testCompClient = testObjClient.GetComponent(); + if (testObjServer.OwnerClientId == testObjClient.OwnerClientId && + testCompServer.OwnerReadWrite_Position.Value == testCompClient.ServerWritable_Position.Value && + testCompServer.OwnerReadWrite_Position.ReadPerm == testCompClient.ServerWritable_Position.ReadPerm && + testCompServer.OwnerReadWrite_Position.WritePerm == testCompClient.ServerWritable_Position.WritePerm) + { + return true; + } + } + return false; + } + + private bool CheckOwnerReadWriteAreNotEqualOnNonOwnerClients(NetVarPermTestComp ownerReadWriteObject) + { + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + var testObjClient = clientNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; + var testCompClient = testObjClient.GetComponent(); + if (testObjClient.OwnerClientId != ownerReadWriteObject.OwnerClientId || + ownerReadWriteObject.OwnerReadWrite_Position.Value == testCompClient.ServerWritable_Position.Value || + ownerReadWriteObject.OwnerReadWrite_Position.ReadPerm != testCompClient.ServerWritable_Position.ReadPerm || + ownerReadWriteObject.OwnerReadWrite_Position.WritePerm != testCompClient.ServerWritable_Position.WritePerm) + { + return false; + } + } + return true; + } + + [UnityTest] + public IEnumerator ServerChangesOwnerWritableNetVar() + { + yield return WaitForOwnerWritableAreEqualOnAll(); + + var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; + var testCompServer = testObjServer.GetComponent(); + + 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)); + + testCompServer.OwnerWritable_Position.Value = newValue; + yield return WaitForPositionsAreEqual(testCompServer.OwnerWritable_Position, newValue); + + yield return WaitForOwnerWritableAreEqualOnAll(); + } + + [UnityTest] + public IEnumerator ServerChangesServerWritableNetVar() + { + yield return WaitForServerWritableAreEqualOnAll(); + + var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; + var testCompServer = testObjServer.GetComponent(); + + var oldValue = testCompServer.ServerWritable_Position.Value; + var newValue = oldValue + new Vector3(Random.Range(0, 100.0f), Random.Range(0, 100.0f), Random.Range(0, 100.0f)); + + testCompServer.ServerWritable_Position.Value = newValue; + yield return WaitForPositionsAreEqual(testCompServer.ServerWritable_Position, newValue); + + yield return WaitForServerWritableAreEqualOnAll(); + } + + [UnityTest] + public IEnumerator ClientChangesOwnerWritableNetVar() + { + yield return WaitForOwnerWritableAreEqualOnAll(); + + var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; + + int clientManagerIndex = m_ClientNetworkManagers.Length - 1; + var newOwnerClientId = m_ClientNetworkManagers[clientManagerIndex].LocalClientId; + testObjServer.ChangeOwnership(newOwnerClientId); + yield return WaitForTicks(m_ServerNetworkManager, 2); + + yield return WaitForOwnerWritableAreEqualOnAll(); + + var testObjClient = m_ClientNetworkManagers[clientManagerIndex].SpawnManager.SpawnedObjects[m_TestObjId]; + var testCompClient = testObjClient.GetComponent(); + + var oldValue = testCompClient.OwnerWritable_Position.Value; + var newValue = oldValue + new Vector3(Random.Range(0, 100.0f), Random.Range(0, 100.0f), Random.Range(0, 100.0f)); + + testCompClient.OwnerWritable_Position.Value = newValue; + yield return WaitForPositionsAreEqual(testCompClient.OwnerWritable_Position, newValue); + + yield return WaitForOwnerWritableAreEqualOnAll(); + } + + /// + /// This tests the scenario where a client owner has both read and write + /// permissions set. The server should be the only instance that can read + /// the NetworkVariable. ServerCannotChangeOwnerWritableNetVar performs + /// the same check to make sure the server cannot write to a client owner + /// NetworkVariable with owner write permissions. + /// + [UnityTest] + public IEnumerator ClientOwnerWithReadWriteChangesNetVar() + { + yield return WaitForOwnerWritableAreEqualOnAll(); + + var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; + + int clientManagerIndex = m_ClientNetworkManagers.Length - 1; + var newOwnerClientId = m_ClientNetworkManagers[clientManagerIndex].LocalClientId; + testObjServer.ChangeOwnership(newOwnerClientId); + yield return WaitForTicks(m_ServerNetworkManager, 2); + + yield return WaitForOwnerWritableAreEqualOnAll(); + + var testObjClient = m_ClientNetworkManagers[clientManagerIndex].SpawnManager.SpawnedObjects[m_TestObjId]; + var testCompClient = testObjClient.GetComponent(); + + var oldValue = testCompClient.OwnerReadWrite_Position.Value; + var newValue = oldValue + new Vector3(Random.Range(0, 100.0f), Random.Range(0, 100.0f), Random.Range(0, 100.0f)); + + testCompClient.OwnerWritable_Position.Value = newValue; + yield return WaitForPositionsAreEqual(testCompClient.OwnerWritable_Position, newValue); + + // Verify the client owner and server match + yield return CheckOwnerReadWriteAreEqualOnOwnerAndServer(); + + // Verify the non-owner clients do not have the same Value but do have the same permissions + yield return CheckOwnerReadWriteAreNotEqualOnNonOwnerClients(testCompClient); + } + + + [UnityTest] + public IEnumerator ClientCannotChangeServerWritableNetVar() + { + yield return WaitForServerWritableAreEqualOnAll(); + + var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; + var testCompServer = testObjServer.GetComponent(); + + int clientManagerIndex = m_ClientNetworkManagers.Length - 1; + var newOwnerClientId = m_ClientNetworkManagers[clientManagerIndex].LocalClientId; + testObjServer.ChangeOwnership(newOwnerClientId); + yield return WaitForTicks(m_ServerNetworkManager, 2); + + yield return WaitForServerWritableAreEqualOnAll(); + + var testObjClient = m_ClientNetworkManagers[clientManagerIndex].SpawnManager.SpawnedObjects[m_TestObjId]; + var testCompClient = testObjClient.GetComponent(); + + 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)); + LogAssert.Expect(LogType.Error, testCompClient.ServerWritable_Position.GetWritePermissionError()); + testCompClient.ServerWritable_Position.Value = newValue; + yield return WaitForPositionsAreEqual(testCompServer.ServerWritable_Position, oldValue); + + yield return WaitForServerWritableAreEqualOnAll(); + + testCompServer.ServerWritable_Position.Value = newValue; + yield return WaitForPositionsAreEqual(testCompServer.ServerWritable_Position, newValue); + + yield return WaitForServerWritableAreEqualOnAll(); + } + + [UnityTest] + public IEnumerator ServerCannotChangeOwnerWritableNetVar() + { + yield return WaitForOwnerWritableAreEqualOnAll(); + + var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; + var testCompServer = testObjServer.GetComponent(); + + int clientManagerIndex = m_ClientNetworkManagers.Length - 1; + var newOwnerClientId = m_ClientNetworkManagers[clientManagerIndex].LocalClientId; + testObjServer.ChangeOwnership(newOwnerClientId); + yield return WaitForTicks(m_ServerNetworkManager, 4); + + yield return WaitForOwnerWritableAreEqualOnAll(); + + 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)); + + LogAssert.Expect(LogType.Error, testCompServer.OwnerWritable_Position.GetWritePermissionError()); + testCompServer.OwnerWritable_Position.Value = newValue; + yield return WaitForPositionsAreEqual(testCompServer.OwnerWritable_Position, oldValue); + + yield return WaitForOwnerWritableAreEqualOnAll(); + + var testObjClient = m_ClientNetworkManagers[clientManagerIndex].SpawnManager.SpawnedObjects[m_TestObjId]; + var testCompClient = testObjClient.GetComponent(); + + testCompClient.OwnerWritable_Position.Value = newValue; + yield return WaitForPositionsAreEqual(testCompClient.OwnerWritable_Position, newValue); + + yield return WaitForOwnerWritableAreEqualOnAll(); + } + } +} +#endif diff --git a/Tests/Runtime/NetworkVariable/NetworkVariablePermissionTests.cs.meta b/Tests/Runtime/NetworkVariable/NetworkVariablePermissionTests.cs.meta new file mode 100644 index 0000000..d06c83f --- /dev/null +++ b/Tests/Runtime/NetworkVariable/NetworkVariablePermissionTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 60d49d322bef8ff4ebb8c4abf57e18e3 \ No newline at end of file diff --git a/Tests/Runtime/NetworkVariableTests.cs b/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs similarity index 92% rename from Tests/Runtime/NetworkVariableTests.cs rename to Tests/Runtime/NetworkVariable/NetworkVariableTests.cs index 0827395..d67d207 100644 --- a/Tests/Runtime/NetworkVariableTests.cs +++ b/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs @@ -12,298 +12,6 @@ namespace Unity.Netcode.RuntimeTests { - [TestFixtureSource(nameof(TestDataSource))] - internal class NetworkVariablePermissionTests : NetcodeIntegrationTest - { - public static IEnumerable TestDataSource() - { - NetworkVariableBase.IgnoreInitializeWarning = true; - foreach (HostOrServer hostOrServer in Enum.GetValues(typeof(HostOrServer))) - { - // DANGO-EXP TODO: Add support for distributed authority mode - if (hostOrServer == HostOrServer.DAHost) - { - continue; - } - yield return new TestFixtureData(hostOrServer); - } - - NetworkVariableBase.IgnoreInitializeWarning = false; - } - - protected override int NumberOfClients => 3; - - public NetworkVariablePermissionTests(HostOrServer hostOrServer) : base(hostOrServer) { } - - private GameObject m_TestObjPrefab; - private ulong m_TestObjId = 0; - - protected override void OnServerAndClientsCreated() - { - m_TestObjPrefab = CreateNetworkObjectPrefab($"[{nameof(NetworkVariablePermissionTests)}.{nameof(m_TestObjPrefab)}]"); - var testComp = m_TestObjPrefab.AddComponent(); - } - - protected override IEnumerator OnServerAndClientsConnected() - { - m_TestObjId = SpawnObject(m_TestObjPrefab, m_ServerNetworkManager).GetComponent().NetworkObjectId; - yield return null; - } - - private IEnumerator WaitForPositionsAreEqual(NetworkVariable netvar, Vector3 expected) - { - yield return WaitForConditionOrTimeOut(() => netvar.Value == expected); - Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut); - } - - private IEnumerator WaitForOwnerWritableAreEqualOnAll() - { - yield return WaitForConditionOrTimeOut(CheckOwnerWritableAreEqualOnAll); - Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut); - } - - private bool CheckOwnerWritableAreEqualOnAll() - { - var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; - var testCompServer = testObjServer.GetComponent(); - foreach (var clientNetworkManager in m_ClientNetworkManagers) - { - var testObjClient = clientNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; - var testCompClient = testObjClient.GetComponent(); - if (testObjServer.OwnerClientId != testObjClient.OwnerClientId || - testCompServer.OwnerWritable_Position.Value != testCompClient.OwnerWritable_Position.Value || - testCompServer.OwnerWritable_Position.ReadPerm != testCompClient.OwnerWritable_Position.ReadPerm || - testCompServer.OwnerWritable_Position.WritePerm != testCompClient.OwnerWritable_Position.WritePerm) - { - return false; - } - } - return true; - } - - private IEnumerator WaitForServerWritableAreEqualOnAll() - { - yield return WaitForConditionOrTimeOut(CheckServerWritableAreEqualOnAll); - Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut); - } - - private bool CheckServerWritableAreEqualOnAll() - { - var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; - var testCompServer = testObjServer.GetComponent(); - foreach (var clientNetworkManager in m_ClientNetworkManagers) - { - var testObjClient = clientNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; - var testCompClient = testObjClient.GetComponent(); - if (testCompServer.ServerWritable_Position.Value != testCompClient.ServerWritable_Position.Value || - testCompServer.ServerWritable_Position.ReadPerm != testCompClient.ServerWritable_Position.ReadPerm || - testCompServer.ServerWritable_Position.WritePerm != testCompClient.ServerWritable_Position.WritePerm) - { - return false; - } - } - return true; - } - - private bool CheckOwnerReadWriteAreEqualOnOwnerAndServer() - { - var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; - var testCompServer = testObjServer.GetComponent(); - foreach (var clientNetworkManager in m_ClientNetworkManagers) - { - var testObjClient = clientNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; - var testCompClient = testObjClient.GetComponent(); - if (testObjServer.OwnerClientId == testObjClient.OwnerClientId && - testCompServer.OwnerReadWrite_Position.Value == testCompClient.ServerWritable_Position.Value && - testCompServer.OwnerReadWrite_Position.ReadPerm == testCompClient.ServerWritable_Position.ReadPerm && - testCompServer.OwnerReadWrite_Position.WritePerm == testCompClient.ServerWritable_Position.WritePerm) - { - return true; - } - } - return false; - } - - private bool CheckOwnerReadWriteAreNotEqualOnNonOwnerClients(NetVarPermTestComp ownerReadWriteObject) - { - foreach (var clientNetworkManager in m_ClientNetworkManagers) - { - var testObjClient = clientNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; - var testCompClient = testObjClient.GetComponent(); - if (testObjClient.OwnerClientId != ownerReadWriteObject.OwnerClientId || - ownerReadWriteObject.OwnerReadWrite_Position.Value == testCompClient.ServerWritable_Position.Value || - ownerReadWriteObject.OwnerReadWrite_Position.ReadPerm != testCompClient.ServerWritable_Position.ReadPerm || - ownerReadWriteObject.OwnerReadWrite_Position.WritePerm != testCompClient.ServerWritable_Position.WritePerm) - { - return false; - } - } - return true; - } - - [UnityTest] - public IEnumerator ServerChangesOwnerWritableNetVar() - { - yield return WaitForOwnerWritableAreEqualOnAll(); - - var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; - var testCompServer = testObjServer.GetComponent(); - - 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)); - - testCompServer.OwnerWritable_Position.Value = newValue; - yield return WaitForPositionsAreEqual(testCompServer.OwnerWritable_Position, newValue); - - yield return WaitForOwnerWritableAreEqualOnAll(); - } - - [UnityTest] - public IEnumerator ServerChangesServerWritableNetVar() - { - yield return WaitForServerWritableAreEqualOnAll(); - - var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; - var testCompServer = testObjServer.GetComponent(); - - var oldValue = testCompServer.ServerWritable_Position.Value; - var newValue = oldValue + new Vector3(Random.Range(0, 100.0f), Random.Range(0, 100.0f), Random.Range(0, 100.0f)); - - testCompServer.ServerWritable_Position.Value = newValue; - yield return WaitForPositionsAreEqual(testCompServer.ServerWritable_Position, newValue); - - yield return WaitForServerWritableAreEqualOnAll(); - } - - [UnityTest] - public IEnumerator ClientChangesOwnerWritableNetVar() - { - yield return WaitForOwnerWritableAreEqualOnAll(); - - var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; - - int clientManagerIndex = m_ClientNetworkManagers.Length - 1; - var newOwnerClientId = m_ClientNetworkManagers[clientManagerIndex].LocalClientId; - testObjServer.ChangeOwnership(newOwnerClientId); - yield return WaitForTicks(m_ServerNetworkManager, 2); - - yield return WaitForOwnerWritableAreEqualOnAll(); - - var testObjClient = m_ClientNetworkManagers[clientManagerIndex].SpawnManager.SpawnedObjects[m_TestObjId]; - var testCompClient = testObjClient.GetComponent(); - - var oldValue = testCompClient.OwnerWritable_Position.Value; - var newValue = oldValue + new Vector3(Random.Range(0, 100.0f), Random.Range(0, 100.0f), Random.Range(0, 100.0f)); - - testCompClient.OwnerWritable_Position.Value = newValue; - yield return WaitForPositionsAreEqual(testCompClient.OwnerWritable_Position, newValue); - - yield return WaitForOwnerWritableAreEqualOnAll(); - } - - /// - /// This tests the scenario where a client owner has both read and write - /// permissions set. The server should be the only instance that can read - /// the NetworkVariable. ServerCannotChangeOwnerWritableNetVar performs - /// the same check to make sure the server cannot write to a client owner - /// NetworkVariable with owner write permissions. - /// - [UnityTest] - public IEnumerator ClientOwnerWithReadWriteChangesNetVar() - { - yield return WaitForOwnerWritableAreEqualOnAll(); - - var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; - - int clientManagerIndex = m_ClientNetworkManagers.Length - 1; - var newOwnerClientId = m_ClientNetworkManagers[clientManagerIndex].LocalClientId; - testObjServer.ChangeOwnership(newOwnerClientId); - yield return WaitForTicks(m_ServerNetworkManager, 2); - - yield return WaitForOwnerWritableAreEqualOnAll(); - - var testObjClient = m_ClientNetworkManagers[clientManagerIndex].SpawnManager.SpawnedObjects[m_TestObjId]; - var testCompClient = testObjClient.GetComponent(); - - var oldValue = testCompClient.OwnerReadWrite_Position.Value; - var newValue = oldValue + new Vector3(Random.Range(0, 100.0f), Random.Range(0, 100.0f), Random.Range(0, 100.0f)); - - testCompClient.OwnerWritable_Position.Value = newValue; - yield return WaitForPositionsAreEqual(testCompClient.OwnerWritable_Position, newValue); - - // Verify the client owner and server match - yield return CheckOwnerReadWriteAreEqualOnOwnerAndServer(); - - // Verify the non-owner clients do not have the same Value but do have the same permissions - yield return CheckOwnerReadWriteAreNotEqualOnNonOwnerClients(testCompClient); - } - - - [UnityTest] - public IEnumerator ClientCannotChangeServerWritableNetVar() - { - yield return WaitForServerWritableAreEqualOnAll(); - - var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; - var testCompServer = testObjServer.GetComponent(); - - int clientManagerIndex = m_ClientNetworkManagers.Length - 1; - var newOwnerClientId = m_ClientNetworkManagers[clientManagerIndex].LocalClientId; - testObjServer.ChangeOwnership(newOwnerClientId); - yield return WaitForTicks(m_ServerNetworkManager, 2); - - yield return WaitForServerWritableAreEqualOnAll(); - - var testObjClient = m_ClientNetworkManagers[clientManagerIndex].SpawnManager.SpawnedObjects[m_TestObjId]; - var testCompClient = testObjClient.GetComponent(); - - 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()); - yield return WaitForPositionsAreEqual(testCompServer.ServerWritable_Position, oldValue); - - yield return WaitForServerWritableAreEqualOnAll(); - - testCompServer.ServerWritable_Position.Value = newValue; - yield return WaitForPositionsAreEqual(testCompServer.ServerWritable_Position, newValue); - - yield return WaitForServerWritableAreEqualOnAll(); - } - - [UnityTest] - public IEnumerator ServerCannotChangeOwnerWritableNetVar() - { - yield return WaitForOwnerWritableAreEqualOnAll(); - - var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; - var testCompServer = testObjServer.GetComponent(); - - int clientManagerIndex = m_ClientNetworkManagers.Length - 1; - var newOwnerClientId = m_ClientNetworkManagers[clientManagerIndex].LocalClientId; - testObjServer.ChangeOwnership(newOwnerClientId); - yield return WaitForTicks(m_ServerNetworkManager, 2); - - yield return WaitForOwnerWritableAreEqualOnAll(); - - 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()); - yield return WaitForPositionsAreEqual(testCompServer.OwnerWritable_Position, oldValue); - - yield return WaitForOwnerWritableAreEqualOnAll(); - - var testObjClient = m_ClientNetworkManagers[clientManagerIndex].SpawnManager.SpawnedObjects[m_TestObjId]; - var testCompClient = testObjClient.GetComponent(); - - testCompClient.OwnerWritable_Position.Value = newValue; - yield return WaitForPositionsAreEqual(testCompClient.OwnerWritable_Position, newValue); - - yield return WaitForOwnerWritableAreEqualOnAll(); - } - } - internal struct TestStruct : INetworkSerializable, IEquatable { public uint SomeInt; @@ -438,93 +146,342 @@ public override void OnNetworkSpawn() } } - -#if !MULTIPLAYER_TOOLS - [TestFixture(true)] -#endif - [TestFixture(false)] - internal class NetworkVariableTests : NetcodeIntegrationTest + /// + /// Handles the more generic conditional logic for NetworkList tests + /// which can be used with the + /// that accepts anything derived from the class + /// as a parameter. + /// + internal class NetworkListTestPredicate : ConditionalPredicateBase { - private const string k_StringTestValue = "abcdefghijklmnopqrstuvwxyz"; - private static readonly FixedString32Bytes k_FixedStringTestValue = k_StringTestValue; - protected override int NumberOfClients => 2; - - private const uint k_TestUInt = 0x12345678; - - private const int k_TestVal1 = 111; - private const int k_TestVal2 = 222; - private const int k_TestVal3 = 333; - - protected override bool m_EnableTimeTravel => true; - protected override bool m_SetupIsACoroutine => false; - protected override bool m_TearDownIsACoroutine => false; + private const int k_MaxRandomValue = 1000; - private static List s_ClientNetworkVariableTestInstances = new List(); - public static void ClientNetworkVariableTestSpawned(NetworkVariableTest networkVariableTest) - { - s_ClientNetworkVariableTestInstances.Add(networkVariableTest); - } + private Dictionary> m_StateFunctions; - // Player1 component on the server + // Player1 component on the Server private NetworkVariableTest m_Player1OnServer; // Player1 component on client1 private NetworkVariableTest m_Player1OnClient1; - private NetworkListTestPredicate m_NetworkListPredicateHandler; - - private readonly bool m_EnsureLengthSafety; + private string m_TestStageFailedMessage; - public NetworkVariableTests(bool ensureLengthSafety) + public enum NetworkListTestStates { - m_EnsureLengthSafety = ensureLengthSafety; + Add, + ContainsLarge, + Contains, + VerifyData, + IndexOf, } - protected override bool CanStartServerAndClients() + private NetworkListTestStates m_NetworkListTestState; + + public void SetNetworkListTestState(NetworkListTestStates networkListTestState) { - return false; + m_NetworkListTestState = networkListTestState; } - protected override void OnOneTimeSetup() + /// + /// Determines if the condition has been reached for the current NetworkListTestState + /// + protected override bool OnHasConditionBeenReached() { - NetworkVariableBase.IgnoreInitializeWarning = true; - base.OnOneTimeSetup(); + var isStateRegistered = m_StateFunctions.ContainsKey(m_NetworkListTestState); + Assert.IsTrue(isStateRegistered); + return m_StateFunctions[m_NetworkListTestState].Invoke(); } - protected override void OnOneTimeTearDown() + /// + /// Provides all information about the players for both sides for simplicity and informative sake. + /// + /// + private string ConditionFailedInfo() { - NetworkVariableBase.IgnoreInitializeWarning = false; - base.OnOneTimeTearDown(); + return $"{m_NetworkListTestState} condition test failed:\n Server List Count: {m_Player1OnServer.TheList.Count} vs Client List Count: {m_Player1OnClient1.TheList.Count}\n" + + $"Server List Count: {m_Player1OnServer.TheLargeList.Count} vs Client List Count: {m_Player1OnClient1.TheLargeList.Count}\n" + + $"Server Delegate Triggered: {m_Player1OnServer.ListDelegateTriggered} | Client Delegate Triggered: {m_Player1OnClient1.ListDelegateTriggered}\n"; } /// - /// This is an adjustment to how the server and clients are started in order - /// to avoid timing issues when running in a stand alone test runner build. + /// When finished, check if a time out occurred and if so assert and provide meaningful information to troubleshoot why /// - private void InitializeServerAndClients(HostOrServer useHost) + protected override void OnFinished() { - s_ClientNetworkVariableTestInstances.Clear(); - m_PlayerPrefab.AddComponent(); - - m_PlayerPrefab.AddComponent(); - m_PlayerPrefab.AddComponent(); - m_PlayerPrefab.AddComponent(); + Assert.IsFalse(TimedOut, $"{nameof(NetworkListTestPredicate)} timed out waiting for the {m_NetworkListTestState} condition to be reached! \n" + ConditionFailedInfo()); + } - m_ServerNetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety = m_EnsureLengthSafety; - m_ServerNetworkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; - foreach (var client in m_ClientNetworkManagers) + // Uses the ArrayOperator and validates that on both sides the count and values are the same + private bool OnVerifyData() + { + // Wait until both sides have the same number of elements + if (m_Player1OnServer.TheList.Count != m_Player1OnClient1.TheList.Count) { - client.NetworkConfig.EnsureNetworkVariableLengthSafety = m_EnsureLengthSafety; - client.NetworkConfig.PlayerPrefab = m_PlayerPrefab; + return false; } - Assert.True(NetcodeIntegrationTestHelpers.Start(useHost == HostOrServer.Host, m_ServerNetworkManager, m_ClientNetworkManagers), "Failed to start server and client instances"); + // Check the client values against the server values to make sure they match + for (int i = 0; i < m_Player1OnServer.TheList.Count; i++) + { + if (m_Player1OnServer.TheList[i] != m_Player1OnClient1.TheList[i]) + { + return false; + } + } + return true; + } - RegisterSceneManagerHandler(); + /// + /// Verifies the data count, values, and that the ListDelegate on both sides was triggered + /// + private bool OnAdd() + { + bool wasTriggerred = m_Player1OnServer.ListDelegateTriggered && m_Player1OnClient1.ListDelegateTriggered; + return wasTriggerred && OnVerifyData(); + } - // Wait for connection on client and server side - var success = WaitForClientsConnectedOrTimeOutWithTimeTravel(); - Assert.True(success, $"Timed-out waiting for all clients to connect!"); + /// + /// The current version of this test only verified the count of the large list, so that is what this does + /// + private bool OnContainsLarge() + { + return m_Player1OnServer.TheLargeList.Count == m_Player1OnClient1.TheLargeList.Count; + } + + /// + /// Tests NetworkList.Contains which also verifies all values are the same on both sides + /// + private bool OnContains() + { + // Wait until both sides have the same number of elements + if (m_Player1OnServer.TheList.Count != m_Player1OnClient1.TheList.Count) + { + return false; + } + + // Parse through all server values and use the NetworkList.Contains method to check if the value is in the list on the client side + foreach (var serverValue in m_Player1OnServer.TheList) + { + if (!m_Player1OnClient1.TheList.Contains(serverValue)) + { + return false; + } + } + return true; + } + + /// + /// Tests NetworkList.IndexOf and verifies that all values are aligned on both sides + /// + private bool OnIndexOf() + { + foreach (var serverSideValue in m_Player1OnServer.TheList) + { + var indexToTest = m_Player1OnServer.TheList.IndexOf(serverSideValue); + if (indexToTest != m_Player1OnServer.TheList.IndexOf(serverSideValue)) + { + return false; + } + } + return true; + } + + public NetworkListTestPredicate(NetworkVariableTest player1OnServer, NetworkVariableTest player1OnClient1, NetworkListTestStates networkListTestState, int elementCount) + { + m_NetworkListTestState = networkListTestState; + m_Player1OnServer = player1OnServer; + m_Player1OnClient1 = player1OnClient1; + m_StateFunctions = new Dictionary> + { + { NetworkListTestStates.Add, OnAdd }, + { NetworkListTestStates.ContainsLarge, OnContainsLarge }, + { NetworkListTestStates.Contains, OnContains }, + { NetworkListTestStates.VerifyData, OnVerifyData }, + { NetworkListTestStates.IndexOf, OnIndexOf } + }; + + if (networkListTestState == NetworkListTestStates.ContainsLarge) + { + for (var i = 0; i < elementCount; ++i) + { + m_Player1OnServer.TheLargeList.Add(new FixedString128Bytes()); + } + } + else + { + for (int i = 0; i < elementCount; i++) + { + m_Player1OnServer.TheList.Add(Random.Range(0, k_MaxRandomValue)); + } + } + } + } + + internal class NetvarDespawnShutdown : NetworkBehaviour + { + private NetworkVariable m_IntNetworkVariable = new NetworkVariable(); + private NetworkList m_IntList; + + private void Awake() + { + m_IntList = new NetworkList(); + } + + public override void OnNetworkDespawn() + { + if (IsServer) + { + m_IntNetworkVariable.Value = 5; + for (int i = 0; i < 10; i++) + { + m_IntList.Add(i); + } + } + base.OnNetworkDespawn(); + } + } + + /// + /// Validates that setting values for NetworkVariable or NetworkList during the + /// OnNetworkDespawn method will not cause an exception to occur. + /// + internal class NetworkVariableModifyOnNetworkDespawn : NetcodeIntegrationTest + { + protected override int NumberOfClients => 1; + + private GameObject m_TestPrefab; + + protected override void OnOneTimeSetup() + { + NetworkVariableBase.IgnoreInitializeWarning = true; + base.OnOneTimeSetup(); + } + + protected override void OnOneTimeTearDown() + { + NetworkVariableBase.IgnoreInitializeWarning = false; + base.OnOneTimeTearDown(); + } + + protected override void OnServerAndClientsCreated() + { + m_TestPrefab = CreateNetworkObjectPrefab("NetVarDespawn"); + m_TestPrefab.AddComponent(); + base.OnServerAndClientsCreated(); + } + + private bool OnClientSpawnedTestPrefab(ulong networkObjectId) + { + var clientId = m_ClientNetworkManagers[0].LocalClientId; + if (!s_GlobalNetworkObjects.ContainsKey(clientId)) + { + return false; + } + + if (!s_GlobalNetworkObjects[clientId].ContainsKey(networkObjectId)) + { + return false; + } + + return true; + } + + [UnityTest] + public IEnumerator ModifyNetworkVariableOrListOnNetworkDespawn() + { + var instance = SpawnObject(m_TestPrefab, m_ServerNetworkManager); + yield return WaitForConditionOrTimeOut(() => OnClientSpawnedTestPrefab(instance.GetComponent().NetworkObjectId)); + m_ServerNetworkManager.Shutdown(); + // As long as no excetptions occur, the test passes. + } + } + +#if !MULTIPLAYER_TOOLS + [TestFixture(true)] +#endif + [TestFixture(false)] + internal class NetworkVariableTests : NetcodeIntegrationTest + { + private const string k_StringTestValue = "abcdefghijklmnopqrstuvwxyz"; + private static readonly FixedString32Bytes k_FixedStringTestValue = k_StringTestValue; + protected override int NumberOfClients => 2; + + private const uint k_TestUInt = 0x12345678; + + private const int k_TestVal1 = 111; + private const int k_TestVal2 = 222; + private const int k_TestVal3 = 333; + + protected override bool m_EnableTimeTravel => true; + protected override bool m_SetupIsACoroutine => false; + protected override bool m_TearDownIsACoroutine => false; + + private static List s_ClientNetworkVariableTestInstances = new List(); + public static void ClientNetworkVariableTestSpawned(NetworkVariableTest networkVariableTest) + { + s_ClientNetworkVariableTestInstances.Add(networkVariableTest); + } + + // Player1 component on the server + private NetworkVariableTest m_Player1OnServer; + + // Player1 component on client1 + private NetworkVariableTest m_Player1OnClient1; + + private NetworkListTestPredicate m_NetworkListPredicateHandler; + + private readonly bool m_EnsureLengthSafety; + + public NetworkVariableTests(bool ensureLengthSafety) + { + m_EnsureLengthSafety = ensureLengthSafety; + } + + protected override bool CanStartServerAndClients() + { + return false; + } + + protected override void OnOneTimeSetup() + { + NetworkVariableBase.IgnoreInitializeWarning = true; + base.OnOneTimeSetup(); + } + + protected override void OnOneTimeTearDown() + { + NetworkVariableBase.IgnoreInitializeWarning = false; + base.OnOneTimeTearDown(); + } + + /// + /// This is an adjustment to how the server and clients are started in order + /// to avoid timing issues when running in a stand alone test runner build. + /// + private void InitializeServerAndClients(HostOrServer useHost) + { + s_ClientNetworkVariableTestInstances.Clear(); + m_PlayerPrefab.AddComponent(); + + m_PlayerPrefab.AddComponent(); + m_PlayerPrefab.AddComponent(); + m_PlayerPrefab.AddComponent(); + + m_ServerNetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety = m_EnsureLengthSafety; + m_ServerNetworkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; + foreach (var client in m_ClientNetworkManagers) + { + client.NetworkConfig.EnsureNetworkVariableLengthSafety = m_EnsureLengthSafety; + client.NetworkConfig.PlayerPrefab = m_PlayerPrefab; + } + + Assert.True(NetcodeIntegrationTestHelpers.Start(useHost == HostOrServer.Host, m_ServerNetworkManager, m_ClientNetworkManagers), "Failed to start server and client instances"); + + RegisterSceneManagerHandler(); + + // Wait for connection on client and server side + var success = WaitForClientsConnectedOrTimeOutWithTimeTravel(); + Assert.True(success, $"Timed-out waiting for all clients to connect!"); // These are the *SERVER VERSIONS* of the *CLIENT PLAYER 1 & 2* var result = new NetcodeIntegrationTestHelpers.ResultWrapper(); @@ -612,7 +569,9 @@ 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; } /// @@ -5130,408 +5089,5 @@ protected override IEnumerator OnTearDown() yield return base.OnTearDown(); } } - - - /// - /// Handles the more generic conditional logic for NetworkList tests - /// which can be used with the - /// that accepts anything derived from the class - /// as a parameter. - /// - internal class NetworkListTestPredicate : ConditionalPredicateBase - { - private const int k_MaxRandomValue = 1000; - - private Dictionary> m_StateFunctions; - - // Player1 component on the Server - private NetworkVariableTest m_Player1OnServer; - - // Player1 component on client1 - private NetworkVariableTest m_Player1OnClient1; - - private string m_TestStageFailedMessage; - - public enum NetworkListTestStates - { - Add, - ContainsLarge, - Contains, - VerifyData, - IndexOf, - } - - private NetworkListTestStates m_NetworkListTestState; - - public void SetNetworkListTestState(NetworkListTestStates networkListTestState) - { - m_NetworkListTestState = networkListTestState; - } - - /// - /// Determines if the condition has been reached for the current NetworkListTestState - /// - protected override bool OnHasConditionBeenReached() - { - var isStateRegistered = m_StateFunctions.ContainsKey(m_NetworkListTestState); - Assert.IsTrue(isStateRegistered); - return m_StateFunctions[m_NetworkListTestState].Invoke(); - } - - /// - /// Provides all information about the players for both sides for simplicity and informative sake. - /// - /// - private string ConditionFailedInfo() - { - return $"{m_NetworkListTestState} condition test failed:\n Server List Count: {m_Player1OnServer.TheList.Count} vs Client List Count: {m_Player1OnClient1.TheList.Count}\n" + - $"Server List Count: {m_Player1OnServer.TheLargeList.Count} vs Client List Count: {m_Player1OnClient1.TheLargeList.Count}\n" + - $"Server Delegate Triggered: {m_Player1OnServer.ListDelegateTriggered} | Client Delegate Triggered: {m_Player1OnClient1.ListDelegateTriggered}\n"; - } - - /// - /// When finished, check if a time out occurred and if so assert and provide meaningful information to troubleshoot why - /// - protected override void OnFinished() - { - Assert.IsFalse(TimedOut, $"{nameof(NetworkListTestPredicate)} timed out waiting for the {m_NetworkListTestState} condition to be reached! \n" + ConditionFailedInfo()); - } - - // Uses the ArrayOperator and validates that on both sides the count and values are the same - private bool OnVerifyData() - { - // Wait until both sides have the same number of elements - if (m_Player1OnServer.TheList.Count != m_Player1OnClient1.TheList.Count) - { - return false; - } - - // Check the client values against the server values to make sure they match - for (int i = 0; i < m_Player1OnServer.TheList.Count; i++) - { - if (m_Player1OnServer.TheList[i] != m_Player1OnClient1.TheList[i]) - { - return false; - } - } - return true; - } - - /// - /// Verifies the data count, values, and that the ListDelegate on both sides was triggered - /// - private bool OnAdd() - { - bool wasTriggerred = m_Player1OnServer.ListDelegateTriggered && m_Player1OnClient1.ListDelegateTriggered; - return wasTriggerred && OnVerifyData(); - } - - /// - /// The current version of this test only verified the count of the large list, so that is what this does - /// - private bool OnContainsLarge() - { - return m_Player1OnServer.TheLargeList.Count == m_Player1OnClient1.TheLargeList.Count; - } - - /// - /// Tests NetworkList.Contains which also verifies all values are the same on both sides - /// - private bool OnContains() - { - // Wait until both sides have the same number of elements - if (m_Player1OnServer.TheList.Count != m_Player1OnClient1.TheList.Count) - { - return false; - } - - // Parse through all server values and use the NetworkList.Contains method to check if the value is in the list on the client side - foreach (var serverValue in m_Player1OnServer.TheList) - { - if (!m_Player1OnClient1.TheList.Contains(serverValue)) - { - return false; - } - } - return true; - } - - /// - /// Tests NetworkList.IndexOf and verifies that all values are aligned on both sides - /// - private bool OnIndexOf() - { - foreach (var serverSideValue in m_Player1OnServer.TheList) - { - var indexToTest = m_Player1OnServer.TheList.IndexOf(serverSideValue); - if (indexToTest != m_Player1OnServer.TheList.IndexOf(serverSideValue)) - { - return false; - } - } - return true; - } - - public NetworkListTestPredicate(NetworkVariableTest player1OnServer, NetworkVariableTest player1OnClient1, NetworkListTestStates networkListTestState, int elementCount) - { - m_NetworkListTestState = networkListTestState; - m_Player1OnServer = player1OnServer; - m_Player1OnClient1 = player1OnClient1; - m_StateFunctions = new Dictionary> - { - { NetworkListTestStates.Add, OnAdd }, - { NetworkListTestStates.ContainsLarge, OnContainsLarge }, - { NetworkListTestStates.Contains, OnContains }, - { NetworkListTestStates.VerifyData, OnVerifyData }, - { NetworkListTestStates.IndexOf, OnIndexOf } - }; - - if (networkListTestState == NetworkListTestStates.ContainsLarge) - { - for (var i = 0; i < elementCount; ++i) - { - m_Player1OnServer.TheLargeList.Add(new FixedString128Bytes()); - } - } - else - { - for (int i = 0; i < elementCount; i++) - { - m_Player1OnServer.TheList.Add(Random.Range(0, k_MaxRandomValue)); - } - } - } - } - - [TestFixtureSource(nameof(TestDataSource))] - internal class NetworkVariableInheritanceTests : NetcodeIntegrationTest - { - public NetworkVariableInheritanceTests(HostOrServer hostOrServer) - : base(hostOrServer) - { - } - - protected override int NumberOfClients => 2; - - public static IEnumerable TestDataSource() => - Enum.GetValues(typeof(HostOrServer)).OfType().Select(x => new TestFixtureData(x)); - - internal class ComponentA : NetworkBehaviour - { - public NetworkVariable PublicFieldA = new NetworkVariable(1); - protected NetworkVariable m_ProtectedFieldA = new NetworkVariable(2); - private NetworkVariable m_PrivateFieldA = new NetworkVariable(3); - - public void ChangeValuesA(int pub, int pro, int pri) - { - PublicFieldA.Value = pub; - m_ProtectedFieldA.Value = pro; - m_PrivateFieldA.Value = pri; - } - - public bool CompareValuesA(ComponentA other) - { - return PublicFieldA.Value == other.PublicFieldA.Value && - m_ProtectedFieldA.Value == other.m_ProtectedFieldA.Value && - m_PrivateFieldA.Value == other.m_PrivateFieldA.Value; - } - } - - internal class ComponentB : ComponentA - { - public NetworkVariable PublicFieldB = new NetworkVariable(11); - protected NetworkVariable m_ProtectedFieldB = new NetworkVariable(22); - private NetworkVariable m_PrivateFieldB = new NetworkVariable(33); - - public void ChangeValuesB(int pub, int pro, int pri) - { - PublicFieldB.Value = pub; - m_ProtectedFieldB.Value = pro; - m_PrivateFieldB.Value = pri; - } - - public bool CompareValuesB(ComponentB other) - { - return PublicFieldB.Value == other.PublicFieldB.Value && - m_ProtectedFieldB.Value == other.m_ProtectedFieldB.Value && - m_PrivateFieldB.Value == other.m_PrivateFieldB.Value; - } - } - - internal class ComponentC : ComponentB - { - public NetworkVariable PublicFieldC = new NetworkVariable(111); - protected NetworkVariable m_ProtectedFieldC = new NetworkVariable(222); - private NetworkVariable m_PrivateFieldC = new NetworkVariable(333); - - public void ChangeValuesC(int pub, int pro, int pri) - { - PublicFieldC.Value = pub; - m_ProtectedFieldA.Value = pro; - m_PrivateFieldC.Value = pri; - } - - public bool CompareValuesC(ComponentC other) - { - return PublicFieldC.Value == other.PublicFieldC.Value && - m_ProtectedFieldC.Value == other.m_ProtectedFieldC.Value && - m_PrivateFieldC.Value == other.m_PrivateFieldC.Value; - } - } - - private GameObject m_TestObjectPrefab; - private ulong m_TestObjectId = 0; - - protected override void OnOneTimeSetup() - { - NetworkVariableBase.IgnoreInitializeWarning = true; - base.OnOneTimeSetup(); - } - - protected override void OnOneTimeTearDown() - { - NetworkVariableBase.IgnoreInitializeWarning = false; - base.OnOneTimeTearDown(); - } - - protected override void OnServerAndClientsCreated() - { - m_TestObjectPrefab = CreateNetworkObjectPrefab($"[{nameof(NetworkVariableInheritanceTests)}.{nameof(m_TestObjectPrefab)}]"); - m_TestObjectPrefab.AddComponent(); - m_TestObjectPrefab.AddComponent(); - m_TestObjectPrefab.AddComponent(); - } - - protected override IEnumerator OnServerAndClientsConnected() - { - var serverTestObject = SpawnObject(m_TestObjectPrefab, m_ServerNetworkManager).GetComponent(); - m_TestObjectId = serverTestObject.NetworkObjectId; - - var serverTestComponentA = serverTestObject.GetComponent(); - var serverTestComponentB = serverTestObject.GetComponent(); - var serverTestComponentC = serverTestObject.GetComponent(); - - serverTestComponentA.ChangeValuesA(1000, 2000, 3000); - serverTestComponentB.ChangeValuesA(1000, 2000, 3000); - serverTestComponentB.ChangeValuesB(1100, 2200, 3300); - serverTestComponentC.ChangeValuesA(1000, 2000, 3000); - serverTestComponentC.ChangeValuesB(1100, 2200, 3300); - serverTestComponentC.ChangeValuesC(1110, 2220, 3330); - - yield return WaitForTicks(m_ServerNetworkManager, 2); - } - - private bool CheckTestObjectComponentValuesOnAll() - { - var serverTestObject = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjectId]; - var serverTestComponentA = serverTestObject.GetComponent(); - var serverTestComponentB = serverTestObject.GetComponent(); - var serverTestComponentC = serverTestObject.GetComponent(); - foreach (var clientNetworkManager in m_ClientNetworkManagers) - { - var clientTestObject = clientNetworkManager.SpawnManager.SpawnedObjects[m_TestObjectId]; - var clientTestComponentA = clientTestObject.GetComponent(); - var clientTestComponentB = clientTestObject.GetComponent(); - var clientTestComponentC = clientTestObject.GetComponent(); - if (!serverTestComponentA.CompareValuesA(clientTestComponentA) || - !serverTestComponentB.CompareValuesA(clientTestComponentB) || - !serverTestComponentB.CompareValuesB(clientTestComponentB) || - !serverTestComponentC.CompareValuesA(clientTestComponentC) || - !serverTestComponentC.CompareValuesB(clientTestComponentC) || - !serverTestComponentC.CompareValuesC(clientTestComponentC)) - { - return false; - } - } - - return true; - } - - [UnityTest] - public IEnumerator TestInheritedFields() - { - yield return WaitForConditionOrTimeOut(CheckTestObjectComponentValuesOnAll); - Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, nameof(CheckTestObjectComponentValuesOnAll)); - } - } - - internal class NetvarDespawnShutdown : NetworkBehaviour - { - private NetworkVariable m_IntNetworkVariable = new NetworkVariable(); - private NetworkList m_IntList; - - private void Awake() - { - m_IntList = new NetworkList(); - } - - public override void OnNetworkDespawn() - { - if (IsServer) - { - m_IntNetworkVariable.Value = 5; - for (int i = 0; i < 10; i++) - { - m_IntList.Add(i); - } - } - base.OnNetworkDespawn(); - } - } - - /// - /// Validates that setting values for NetworkVariable or NetworkList during the - /// OnNetworkDespawn method will not cause an exception to occur. - /// - internal class NetworkVariableModifyOnNetworkDespawn : NetcodeIntegrationTest - { - protected override int NumberOfClients => 1; - - private GameObject m_TestPrefab; - - protected override void OnOneTimeSetup() - { - NetworkVariableBase.IgnoreInitializeWarning = true; - base.OnOneTimeSetup(); - } - - protected override void OnOneTimeTearDown() - { - NetworkVariableBase.IgnoreInitializeWarning = false; - base.OnOneTimeTearDown(); - } - - protected override void OnServerAndClientsCreated() - { - m_TestPrefab = CreateNetworkObjectPrefab("NetVarDespawn"); - m_TestPrefab.AddComponent(); - base.OnServerAndClientsCreated(); - } - - private bool OnClientSpawnedTestPrefab(ulong networkObjectId) - { - var clientId = m_ClientNetworkManagers[0].LocalClientId; - if (!s_GlobalNetworkObjects.ContainsKey(clientId)) - { - return false; - } - - if (!s_GlobalNetworkObjects[clientId].ContainsKey(networkObjectId)) - { - return false; - } - - return true; - } - - [UnityTest] - public IEnumerator ModifyNetworkVariableOrListOnNetworkDespawn() - { - var instance = SpawnObject(m_TestPrefab, m_ServerNetworkManager); - yield return WaitForConditionOrTimeOut(() => OnClientSpawnedTestPrefab(instance.GetComponent().NetworkObjectId)); - m_ServerNetworkManager.Shutdown(); - // As long as no excetptions occur, the test passes. - } - } } #endif diff --git a/Tests/Runtime/NetworkVariableTests.cs.meta b/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs.meta similarity index 100% rename from Tests/Runtime/NetworkVariableTests.cs.meta rename to Tests/Runtime/NetworkVariable/NetworkVariableTests.cs.meta diff --git a/Tests/Runtime/NetworkVariableTestsHelperTypes.cs b/Tests/Runtime/NetworkVariable/NetworkVariableTestsHelperTypes.cs similarity index 100% rename from Tests/Runtime/NetworkVariableTestsHelperTypes.cs rename to Tests/Runtime/NetworkVariable/NetworkVariableTestsHelperTypes.cs diff --git a/Tests/Runtime/NetworkVariableTestsHelperTypes.cs.meta b/Tests/Runtime/NetworkVariable/NetworkVariableTestsHelperTypes.cs.meta similarity index 100% rename from Tests/Runtime/NetworkVariableTestsHelperTypes.cs.meta rename to Tests/Runtime/NetworkVariable/NetworkVariableTestsHelperTypes.cs.meta diff --git a/Tests/Runtime/NetworkVariableTraitsTests.cs b/Tests/Runtime/NetworkVariable/NetworkVariableTraitsTests.cs similarity index 100% rename from Tests/Runtime/NetworkVariableTraitsTests.cs rename to Tests/Runtime/NetworkVariable/NetworkVariableTraitsTests.cs diff --git a/Tests/Runtime/NetworkVariableTraitsTests.cs.meta b/Tests/Runtime/NetworkVariable/NetworkVariableTraitsTests.cs.meta similarity index 100% rename from Tests/Runtime/NetworkVariableTraitsTests.cs.meta rename to Tests/Runtime/NetworkVariable/NetworkVariableTraitsTests.cs.meta diff --git a/Tests/Runtime/NetworkVariableUserSerializableTypesTests.cs b/Tests/Runtime/NetworkVariable/NetworkVariableUserSerializableTypesTests.cs similarity index 100% rename from Tests/Runtime/NetworkVariableUserSerializableTypesTests.cs rename to Tests/Runtime/NetworkVariable/NetworkVariableUserSerializableTypesTests.cs diff --git a/Tests/Runtime/NetworkVariableUserSerializableTypesTests.cs.meta b/Tests/Runtime/NetworkVariable/NetworkVariableUserSerializableTypesTests.cs.meta similarity index 100% rename from Tests/Runtime/NetworkVariableUserSerializableTypesTests.cs.meta rename to Tests/Runtime/NetworkVariable/NetworkVariableUserSerializableTypesTests.cs.meta diff --git a/Tests/Runtime/OwnerModifiedTests.cs b/Tests/Runtime/NetworkVariable/OwnerModifiedTests.cs similarity index 100% rename from Tests/Runtime/OwnerModifiedTests.cs rename to Tests/Runtime/NetworkVariable/OwnerModifiedTests.cs diff --git a/Tests/Runtime/OwnerModifiedTests.cs.meta b/Tests/Runtime/NetworkVariable/OwnerModifiedTests.cs.meta similarity index 100% rename from Tests/Runtime/OwnerModifiedTests.cs.meta rename to Tests/Runtime/NetworkVariable/OwnerModifiedTests.cs.meta diff --git a/Tests/Runtime/OwnerPermissionTests.cs b/Tests/Runtime/NetworkVariable/OwnerPermissionTests.cs similarity index 72% rename from Tests/Runtime/OwnerPermissionTests.cs rename to Tests/Runtime/NetworkVariable/OwnerPermissionTests.cs index 8ff4098..01878e1 100644 --- a/Tests/Runtime/OwnerPermissionTests.cs +++ b/Tests/Runtime/NetworkVariable/OwnerPermissionTests.cs @@ -1,4 +1,3 @@ -using System; using System.Collections; using System.Collections.Generic; using Unity.Netcode.TestHelpers.Runtime; @@ -50,7 +49,6 @@ public static void VerifyConsistency() public override void OnNetworkSpawn() { Objects[CurrentlySpawning, NetworkManager.LocalClientId] = GetComponent(); - //Debug.Log($"Object index ({CurrentlySpawning}) spawned on client {NetworkManager.LocalClientId}"); } private void Awake() @@ -85,6 +83,8 @@ public void ListServerChanged(NetworkListEvent listEvent) } } + + internal class OwnerPermissionHideTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; @@ -130,70 +130,44 @@ public IEnumerator OwnerPermissionTest() for (var clientWriting = 0; clientWriting < 3; clientWriting++) { // ==== Server-writable NetworkVariable ==== - var gotException = false; VerboseDebug($"Writing to server-write variable on object {objectIndex} on client {clientWriting}"); - try - { - nextValueToWrite++; - OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkVariableServer.Value = nextValueToWrite; - } - catch (Exception) + nextValueToWrite++; + if (clientWriting != serverIndex) { - gotException = true; + LogAssert.Expect(LogType.Error, OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkVariableServer.GetWritePermissionError()); } - - // 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; VerboseDebug($"Writing to owner-write variable on object {objectIndex} on client {clientWriting}"); - try - { - nextValueToWrite++; - OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkVariableOwner.Value = nextValueToWrite; - } - catch (Exception) + nextValueToWrite++; + if (clientWriting != objectIndex) { - gotException = true; + LogAssert.Expect(LogType.Error, OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkVariableOwner.GetWritePermissionError()); } - - // 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; + VerboseDebug($"Writing to [Add] server-write NetworkList on object {objectIndex} on client {clientWriting}"); - try - { - nextValueToWrite++; - OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkListServer.Add(nextValueToWrite); - } - catch (Exception) + nextValueToWrite++; + if (clientWriting != serverIndex) { - gotException = true; + LogAssert.Expect(LogType.Error, OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkListServer.GetWritePermissionError()); } - - // 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; + 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/Tests/Runtime/OwnerPermissionTests.cs.meta b/Tests/Runtime/NetworkVariable/OwnerPermissionTests.cs.meta similarity index 100% rename from Tests/Runtime/OwnerPermissionTests.cs.meta rename to Tests/Runtime/NetworkVariable/OwnerPermissionTests.cs.meta diff --git a/package.json b/package.json index 28a1581..961f571 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": "2.0.0-pre.3", + "version": "2.0.0-pre.4", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", "com.unity.transport": "2.3.0" }, "_upm": { - "changelog": "### Added\n- Added: `UnityTransport.GetNetworkDriver` and `UnityTransport.GetLocalEndpoint` methods to expose the driver and local endpoint being used. (#2978)\n\n### Fixed\n\n- Fixed issue where deferred despawn was causing GC allocations when converting an `IEnumerable` to a list. (#2983)\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. (#2979)\n- Fixed issue where `NetworkManager.ScenesLoaded` was not being updated if `PostSynchronizationSceneUnloading` was set and any loaded scenes not used during synchronization were unloaded. (#2971)\n- Fixed issue where `Rigidbody2d` under Unity 6000.0.11f1 has breaking changes where `velocity` is now `linearVelocity` and `isKinematic` is replaced by `bodyType`. (#2971)\n- Fixed issue where `NetworkSpawnManager.InstantiateAndSpawn` and `NetworkObject.InstantiateAndSpawn` were not honoring the ownerClientId parameter when using a client-server network topology. (#2968)\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.(#2962)\n- Fixed issue when scene management was disabled and the session owner would still try to synchronize a late joining client. (#2962)\n- Fixed issue when using a distributed authority network topology where it would allow a session owner to spawn a `NetworkObject` prior to being approved. Now, an error message is logged and the `NetworkObject` will not be spawned prior to the client being approved. (#2962)\n- Fixed issue where attempting to spawn during `NetworkBehaviour.OnInSceneObjectsSpawned` and `NetworkBehaviour.OnNetworkSessionSynchronized` notifications would throw a collection modified exception. (#2962)\n\n### Changed\n\n- Changed logic where clients can now set the `NetworkSceneManager` client synchronization mode when using a distributed authority network topology. (#2985)" + "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. (#3004)\n\n### Fixed\n\n- Fixed issue where nested `NetworkTransform` components were not getting updated. (#3016)\n- Fixed issue by adding null checks in `NetworkVariableBase.CanClientRead` and `NetworkVariableBase.CanClientWrite` methods to ensure safe access to `NetworkBehaviour`. (#3012)\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. (#3009)\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. (#3008)\n- Fixed issue using collections within `NetworkVariable` where the collection would not detect changes to items or nested items. (#3004)\n- Fixed issue where `List`, `Dictionary`, and `HashSet` collections would not uniquely duplicate nested collections. (#3004)\n- Fixed issue where `NotAuthorityTarget` would include the service observer in the list of targets to send the RPC to as opposed to excluding the service observer as it should. (#3000)\n- Fixed issue where `ProxyRpcTargetGroup` could attempt to send a message if there were no targets to send to. (#3000)\n\n### Changed\n\n- Changed `NetworkAnimator` to automatically switch to owner authoritative mode when using a distributed authority network topology. (#3021)\n- Changed permissions exception thrown in `NetworkList` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3004)\n- Changed permissions exception thrown in `NetworkVariable.Value` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3004)" }, "upmCi": { - "footprint": "fbae2629229fb08020f4b9cef5656e6fdf517c3d" + "footprint": "48286e9f7b0e053fe7f7b524bafc69a99c2906fc" }, "documentationUrl": "https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.0/manual/index.html", "repository": { "url": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git", "type": "git", - "revision": "8575c902227d221f987d9cb869d501749f8631b4" + "revision": "2802dfcd13c3be1ac356191cc87d1559203d2db3" }, "samples": [ {