diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 11adaeaa6a..6b3a659242 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -16,6 +16,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Better error message when using generic IEquatable in a generic INetworkSerializable class and updated documentation with workaround. (#3739) - The `NetworkManager` functions `GetTransportIdFromClientId` and `GetClientIdFromTransportId` will now return `ulong.MaxValue` when the clientId or transportId do not exist. (#3707) - Changed NetworkShow to send a message at the end of the frame and force a NetworkVariable synchronization prior to generating the CreateObjectMessage as opposed to waiting until the next network tick to synchronize the show with the update to NetworkVariables. (#3664) - Changed NetworkTransform now synchronizes `NetworkTransform.SwitchTransformSpaceWhenParented` when it is updated by the motion model authority. (#3664) @@ -37,6 +38,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed NetworkTransform state synchronization issue when `NetworkTransform.SwitchTransformSpaceWhenParented` is enabled and the associated NetworkObject is parented multiple times in a single frame or within a couple of frames. (#3664) - Fixed issue when spawning, parenting, and immediately re-parenting when `NetworkTransform.SwitchTransformSpaceWhenParented` is enabled. (#3664) - Fixed issue where the disconnect event and provided message was too generic to know why the disconnect occurred. (#3551) +- Exception when the network prefab list in the network manager has uninitialized elements. (#3739) ### Security diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializable.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializable.md index e75f9a9c49..3f7dcd689e 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializable.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializable.md @@ -181,3 +181,55 @@ public struct MyStructB : MyStructA } } ``` + +## Generic IEquatable network variables + +Generic `INetworkSerializable` types with generic `IEquatable` are not supported, implemented as `public class NotSupported : INetworkSerializable, IEquatable>` where the type would be passed in during declaration like `NetworkVariable> myVar;`. + +The recommended workaround for this would be to create the generic class as usual but add a virtual method for handling the serialization of the type. Then wrap this generic `INetworkSerializable` in a derived class which then needs to have a serializable type defined where the implementation for the serialization is provided. + +For example: + +```csharp +public class MyGameData : INetworkSerializable +{ + // This needs to be a serializable type according to what network variables support + public T Data; + + protected virtual void OnNetworkSerialize(BufferSerializer serializer) where T2 : IReaderWriter + { + } + + public void NetworkSerialize(BufferSerializer serializer) where T2 : IReaderWriter + { + OnNetworkSerialize(serializer); + } +} + +public class GameDataWithLong : MyGameData, IEquatable +{ + // Potential additional data + public int AdditionalData; + + protected virtual bool OnEquals(GameDataWithLong other) + { + return other.Data.Equals(other); + } + public bool Equals(GameDataWithLong other) + { + return OnEquals(other); + } + + protected override void OnNetworkSerialize(BufferSerializer serializer) + { + serializer.SerializeValue(ref AdditionalData); + serializer.SerializeValue(ref Data); + } +} +``` + +Then declare this network variable like so: + +```csharp +NetworkVariable myVar = new NetworkVariable(); +``` \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index 54271e1b65..e6e70d69f6 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -409,7 +409,14 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly, } else { - m_Diagnostics.AddError($"{type}: Managed type in NetworkVariable must implement IEquatable<{type}>"); + foreach (var typeInterface in type.Resolve().Interfaces) + { + if (typeInterface.InterfaceType.Name.Contains(typeof(IEquatable<>).Name) && typeInterface.InterfaceType.IsGenericInstance) + { + m_Diagnostics.AddError($"{type}: A generic IEquatable '{typeInterface.InterfaceType.FullName}' is not supported."); + } + } + m_Diagnostics.AddError($"{type}: Managed type in NetworkVariable must implement IEquatable<{type}>."); equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef); } diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs index 69e5de3ee0..b5f0879118 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using Unity.Netcode.Editor.Configuration; using UnityEditor; using UnityEngine; @@ -301,6 +302,10 @@ private void DisplayNetworkManagerProperties() { EditorGUILayout.HelpBox("You have no prefab list selected. You will have to add your prefabs manually at runtime for netcode to work.", MessageType.Warning); } + else if (m_NetworkManager.NetworkConfig.Prefabs.NetworkPrefabsLists.All(x => x == null)) + { + EditorGUILayout.HelpBox("All prefab lists selected are uninitialized. You will have to add your prefabs manually at runtime for netcode to work.", MessageType.Warning); + } EditorGUILayout.PropertyField(m_PrefabsList); } diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs index 77b30700b7..45d93ad9d7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs @@ -94,6 +94,7 @@ internal void Shutdown() public void Initialize(bool warnInvalid = true) { m_Prefabs.Clear(); + NetworkPrefabsLists.RemoveAll(x => x == null); foreach (var list in NetworkPrefabsLists) { list.OnAdd += AddTriggeredByNetworkPrefabList; diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs index 78b13e3abc..d3dd92de2d 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs @@ -294,6 +294,22 @@ public void WhenModifyingPrefabListUsingPrefabsAPI_ModificationIsLocal() } } + [Test] + public void WhenThereAreUninitializedElementsInPrefabsList_NoErrors() + { + var networkConfig = new NetworkConfig(); + + networkConfig.Prefabs.NetworkPrefabsLists = new List { null }; + + networkConfig.InitializePrefabs(); + + // Null elements will be removed from the list so it should be empty + Assert.IsTrue(networkConfig.Prefabs.NetworkPrefabsLists.Count == 0); + Assert.IsTrue(networkConfig.Prefabs.Prefabs.Count == 0); + + networkConfig.Prefabs.Shutdown(); + } + [Test] public void WhenModifyingPrefabListUsingPrefabsListAPI_ModificationIsShared() {