chore: migrate 2830 allow null references in NetworkBehaviourReference and NetworkObjectReference (#2874)

* initial commit

* update

adding changelog entry

* update and style

Fixing some style issues along with property type issues.

* test

Adding some generalized testing to validate both NetworkBehaviourReference and NetworkObjectReference can be created using a null and serialized when null.

* update

Updating the changed description.

---------

Co-authored-by: Simone Guggiari <simogecko@gmail.com>
This commit is contained in:
Noel Stephens 2024-04-05 16:06:01 -05:00 коммит произвёл GitHub
Родитель 3ca359d05f
Коммит d90292da99
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
5 изменённых файлов: 119 добавлений и 34 удалений

Просмотреть файл

@ -32,6 +32,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Changed
- Changed `NetworkObjectReference` and `NetworkBehaviourReference` to allow null references when constructing and serializing. (#2874)
- Changed `NetworkAnimator` no longer requires the `Animator` component to exist on the same `GameObject`. (#2872)
- Changed `NetworkTransform` to now use `NetworkTransformMessage` as opposed to named messages for NetworkTransformState updates. (#2810)
- Changed `CustomMessageManager` so it no longer attempts to register or "unregister" a null or empty string and will log an error if this condition occurs. (#2807)

Просмотреть файл

@ -11,6 +11,7 @@ namespace Unity.Netcode
{
private NetworkObjectReference m_NetworkObjectReference;
private ushort m_NetworkBehaviourId;
private static ushort s_NullId = ushort.MaxValue;
/// <summary>
/// Creates a new instance of the <see cref="NetworkBehaviourReference{T}"/> struct.
@ -21,7 +22,9 @@ namespace Unity.Netcode
{
if (networkBehaviour == null)
{
throw new ArgumentNullException(nameof(networkBehaviour));
m_NetworkObjectReference = new NetworkObjectReference((NetworkObject)null);
m_NetworkBehaviourId = s_NullId;
return;
}
if (networkBehaviour.NetworkObject == null)
{
@ -60,6 +63,10 @@ namespace Unity.Netcode
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static NetworkBehaviour GetInternal(NetworkBehaviourReference networkBehaviourRef, NetworkManager networkManager = null)
{
if (networkBehaviourRef.m_NetworkBehaviourId == s_NullId)
{
return null;
}
if (networkBehaviourRef.m_NetworkObjectReference.TryGet(out NetworkObject networkObject, networkManager))
{
return networkObject.GetNetworkBehaviourAtOrderIndex(networkBehaviourRef.m_NetworkBehaviourId);

Просмотреть файл

@ -10,6 +10,7 @@ namespace Unity.Netcode
public struct NetworkObjectReference : INetworkSerializable, IEquatable<NetworkObjectReference>
{
private ulong m_NetworkObjectId;
private static ulong s_NullId = ulong.MaxValue;
/// <summary>
/// The <see cref="NetworkObject.NetworkObjectId"/> of the referenced <see cref="NetworkObject"/>.
@ -24,13 +25,13 @@ namespace Unity.Netcode
/// Creates a new instance of the <see cref="NetworkObjectReference"/> struct.
/// </summary>
/// <param name="networkObject">The <see cref="NetworkObject"/> to reference.</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public NetworkObjectReference(NetworkObject networkObject)
{
if (networkObject == null)
{
throw new ArgumentNullException(nameof(networkObject));
m_NetworkObjectId = s_NullId;
return;
}
if (networkObject.IsSpawned == false)
@ -45,16 +46,20 @@ namespace Unity.Netcode
/// Creates a new instance of the <see cref="NetworkObjectReference"/> struct.
/// </summary>
/// <param name="gameObject">The GameObject from which the <see cref="NetworkObject"/> component will be referenced.</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public NetworkObjectReference(GameObject gameObject)
{
if (gameObject == null)
{
throw new ArgumentNullException(nameof(gameObject));
m_NetworkObjectId = s_NullId;
return;
}
var networkObject = gameObject.GetComponent<NetworkObject>() ?? throw new ArgumentException($"Cannot create {nameof(NetworkObjectReference)} from {nameof(GameObject)} without a {nameof(NetworkObject)} component.");
var networkObject = gameObject.GetComponent<NetworkObject>();
if (!networkObject)
{
throw new ArgumentException($"Cannot create {nameof(NetworkObjectReference)} from {nameof(GameObject)} without a {nameof(NetworkObject)} component.");
}
if (networkObject.IsSpawned == false)
{
throw new ArgumentException($"{nameof(NetworkObjectReference)} can only be created from spawned {nameof(NetworkObject)}s.");
@ -80,10 +85,14 @@ namespace Unity.Netcode
/// </summary>
/// <param name="networkObjectRef">The reference.</param>
/// <param name="networkManager">The networkmanager. Uses <see cref="NetworkManager.Singleton"/> to resolve if null.</param>
/// <returns>The resolves <see cref="NetworkObject"/>. Returns null if the networkobject was not found</returns>
/// <returns>The resolved <see cref="NetworkObject"/>. Returns null if the networkobject was not found</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static NetworkObject Resolve(NetworkObjectReference networkObjectRef, NetworkManager networkManager = null)
{
if (networkObjectRef.m_NetworkObjectId == s_NullId)
{
return null;
}
networkManager = networkManager ?? NetworkManager.Singleton;
networkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectRef.m_NetworkObjectId, out NetworkObject networkObject);

Просмотреть файл

@ -17,6 +17,8 @@ namespace Unity.Netcode.RuntimeTests
{
private class TestNetworkBehaviour : NetworkBehaviour
{
public static bool ReceivedRPC;
public NetworkVariable<NetworkBehaviourReference> TestVariable = new NetworkVariable<NetworkBehaviourReference>();
public TestNetworkBehaviour RpcReceivedBehaviour;
@ -25,6 +27,7 @@ namespace Unity.Netcode.RuntimeTests
public void SendReferenceServerRpc(NetworkBehaviourReference value)
{
RpcReceivedBehaviour = (TestNetworkBehaviour)value;
ReceivedRPC = true;
}
}
@ -57,8 +60,43 @@ namespace Unity.Netcode.RuntimeTests
Assert.AreEqual(testNetworkBehaviour, testNetworkBehaviour.RpcReceivedBehaviour);
}
[UnityTest]
public IEnumerator TestSerializeNull([Values] bool initializeWithNull)
{
TestNetworkBehaviour.ReceivedRPC = false;
using var networkObjectContext = UnityObjectContext.CreateNetworkObject();
var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent<TestNetworkBehaviour>();
networkObjectContext.Object.Spawn();
using var otherObjectContext = UnityObjectContext.CreateNetworkObject();
otherObjectContext.Object.Spawn();
// If not initializing with null, then use the default constructor with no assigned NetworkBehaviour
if (!initializeWithNull)
{
testNetworkBehaviour.SendReferenceServerRpc(new NetworkBehaviourReference());
}
else // Otherwise, initialize and pass in null as the reference
{
testNetworkBehaviour.SendReferenceServerRpc(new NetworkBehaviourReference(null));
}
// wait for rpc completion
float t = 0;
while (!TestNetworkBehaviour.ReceivedRPC)
{
t += Time.deltaTime;
if (t > 5f)
{
new AssertionException("RPC with NetworkBehaviour reference hasn't been received");
}
yield return null;
}
// validate
Assert.AreEqual(null, testNetworkBehaviour.RpcReceivedBehaviour);
}
[UnityTest]
public IEnumerator TestRpcImplicitNetworkBehaviour()
@ -89,6 +127,7 @@ namespace Unity.Netcode.RuntimeTests
Assert.AreEqual(testNetworkBehaviour, testNetworkBehaviour.RpcReceivedBehaviour);
}
[Test]
public void TestNetworkVariable()
{
@ -131,15 +170,6 @@ namespace Unity.Netcode.RuntimeTests
});
}
[Test]
public void FailSerializeNullBehaviour()
{
Assert.Throws<ArgumentNullException>(() =>
{
NetworkBehaviourReference outReference = null;
});
}
public void Dispose()
{
//Stop, shutdown, and destroy

Просмотреть файл

@ -19,6 +19,7 @@ namespace Unity.Netcode.RuntimeTests
{
private class TestNetworkBehaviour : NetworkBehaviour
{
public static bool ReceivedRPC;
public NetworkVariable<NetworkObjectReference> TestVariable = new NetworkVariable<NetworkObjectReference>();
public NetworkObject RpcReceivedNetworkObject;
@ -28,6 +29,7 @@ namespace Unity.Netcode.RuntimeTests
[ServerRpc]
public void SendReferenceServerRpc(NetworkObjectReference value)
{
ReceivedRPC = true;
RpcReceivedGameObject = value;
RpcReceivedNetworkObject = value;
}
@ -150,6 +152,60 @@ namespace Unity.Netcode.RuntimeTests
Assert.AreEqual(networkObject, result);
}
public enum NetworkObjectConstructorTypes
{
None,
NullNetworkObject,
NullGameObject
}
[UnityTest]
public IEnumerator TestSerializeNull([Values] NetworkObjectConstructorTypes networkObjectConstructorTypes)
{
TestNetworkBehaviour.ReceivedRPC = false;
using var networkObjectContext = UnityObjectContext.CreateNetworkObject();
var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent<TestNetworkBehaviour>();
networkObjectContext.Object.Spawn();
switch (networkObjectConstructorTypes)
{
case NetworkObjectConstructorTypes.None:
{
testNetworkBehaviour.SendReferenceServerRpc(new NetworkObjectReference());
break;
}
case NetworkObjectConstructorTypes.NullNetworkObject:
{
testNetworkBehaviour.SendReferenceServerRpc(new NetworkObjectReference((NetworkObject)null));
break;
}
case NetworkObjectConstructorTypes.NullGameObject:
{
testNetworkBehaviour.SendReferenceServerRpc(new NetworkObjectReference((GameObject)null));
break;
}
}
// wait for rpc completion
float t = 0;
while (!TestNetworkBehaviour.ReceivedRPC)
{
t += Time.deltaTime;
if (t > 5f)
{
new AssertionException("RPC with NetworkBehaviour reference hasn't been received");
}
yield return null;
}
// validate
Assert.AreEqual(null, testNetworkBehaviour.RpcReceivedNetworkObject);
Assert.AreEqual(null, testNetworkBehaviour.RpcReceivedGameObject);
}
[UnityTest]
public IEnumerator TestRpc()
{
@ -305,24 +361,6 @@ namespace Unity.Netcode.RuntimeTests
});
}
[Test]
public void FailSerializeNullNetworkObject()
{
Assert.Throws<ArgumentNullException>(() =>
{
NetworkObjectReference outReference = (NetworkObject)null;
});
}
[Test]
public void FailSerializeNullGameObject()
{
Assert.Throws<ArgumentNullException>(() =>
{
NetworkObjectReference outReference = (GameObject)null;
});
}
public void Dispose()
{
//Stop, shutdown, and destroy