Skip to content
Merged
2 changes: 2 additions & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,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)
Expand All @@ -36,6 +37,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.

### Security

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -621,3 +621,55 @@ public class TestFixedString : NetworkBehaviour

> [!NOTE]
> The above example uses a pre-set list of strings to cycle through for example purposes only. If you have a predefined set of text strings as part of your actual design then you would not want to use a FixedString to handle synchronizing the changes to `m_TextString`. Instead, you would want to use a `uint` for the type `T` where the `uint` was the index of the string message to apply to `m_TextString`.

## Generic IEquatable network variables

Generic `INetworkSerializable` types with generic `IEquatable` are not supported, implemented as `public class NotSupported<T> : INetworkSerializable, IEquatable<NotSupported<T>>` where the type would be passed in during declaration like `NetworkVariable<NotSupported<int>> 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 FirstGenClass<T> : INetworkSerializable
{
// This needs to be a serializable type according to what network variables support
public T Data;

protected virtual void OnNetworkSerialize<T2>(BufferSerializer<T2> serializer) where T2 : IReaderWriter
{
}

public void NetworkSerialize<T2>(BufferSerializer<T2> serializer) where T2 : IReaderWriter
{
OnNetworkSerialize(serializer);
}
}

public class SecondGenWithLong : FirstGenClass<long>, IEquatable<SecondGenWithLong>
{
// Potential additional data
public int AdditionalData;

protected virtual bool OnEquals(SecondGenWithLong other)
{
return other.Data.Equals(other);
}
public bool Equals(SecondGenWithLong other)
{
return OnEquals(other);
}

protected override void OnNetworkSerialize<T2>(BufferSerializer<T2> serializer)
{
serializer.SerializeValue(ref AdditionalData);
serializer.SerializeValue(ref Data);
}
}
```

Then declare this network variable like so:

```csharp
NetworkVariable<SecondGenWithLong> myVar = new NetworkVariable<SecondGenWithLong>();
```
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ internal void Shutdown()
{
foreach (var list in NetworkPrefabsLists)
{
if (list == null)
{
continue;
}
list.OnAdd -= AddTriggeredByNetworkPrefabList;
list.OnRemove -= RemoveTriggeredByNetworkPrefabList;
}
Expand All @@ -96,6 +100,10 @@ public void Initialize(bool warnInvalid = true)
m_Prefabs.Clear();
foreach (var list in NetworkPrefabsLists)
{
if (list == null)
{
continue;
}
list.OnAdd += AddTriggeredByNetworkPrefabList;
list.OnRemove += RemoveTriggeredByNetworkPrefabList;
}
Expand All @@ -109,6 +117,10 @@ public void Initialize(bool warnInvalid = true)
{
foreach (var list in NetworkPrefabsLists)
{
if (list == null)
{
continue;
}
prefabs.AddRange(list.PrefabList);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,36 @@ public void WhenModifyingPrefabListUsingPrefabsAPI_ModificationIsLocal()
}
}

[Test]
public void WhenThereAreUninitializedElementsInPrefabsList_NoErrors()
{
// Setup
var networkManagerObject = new GameObject();
var networkManager = networkManagerObject.AddComponent<NetworkManager>();
networkManager.NetworkConfig = new NetworkConfig
{
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
};

try
{
networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { null };
networkManager.Initialize(true);

Assert.IsTrue(networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists.Count == 1);
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists[0] == null);
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Prefabs.Count == 0);
}
finally
{
networkManager.ShutdownInternal();
// Shutdown doesn't get called correctly because we called Initialize()
// instead of calling StartHost/StartClient/StartServer. See MTT-860 for
// why.
networkManager.NetworkConfig?.NetworkTransport.Shutdown();
}
}

[Test]
public void WhenModifyingPrefabListUsingPrefabsListAPI_ModificationIsShared()
{
Expand Down