From 8d04f96f5d8715524983b02dbbd09983fb809d99 Mon Sep 17 00:00:00 2001 From: Simone Guggiari Date: Wed, 21 Feb 2024 10:33:43 +0100 Subject: [PATCH 1/5] initial commit --- .../NetworkBehaviourReference.cs | 7 ++++++- .../Serialization/NetworkObjectReference.cs | 19 +++++++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs index 6df91b8f55..f20e98375c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs @@ -11,6 +11,7 @@ public struct NetworkBehaviourReference : INetworkSerializable, IEquatable /// Creates a new instance of the struct. @@ -21,7 +22,9 @@ public NetworkBehaviourReference(NetworkBehaviour networkBehaviour) { if (networkBehaviour == null) { - throw new ArgumentNullException(nameof(networkBehaviour)); + m_NetworkObjectReference = new NetworkObjectReference((NetworkObject)null); + m_NetworkBehaviourId = nullId; + return; } if (networkBehaviour.NetworkObject == null) { @@ -60,6 +63,8 @@ public bool TryGet(out T networkBehaviour, NetworkManager networkManager = nu [MethodImpl(MethodImplOptions.AggressiveInlining)] private static NetworkBehaviour GetInternal(NetworkBehaviourReference networkBehaviourRef, NetworkManager networkManager = null) { + if (m_NetworkBehaviourId == nullId) + return null; if (networkBehaviourRef.m_NetworkObjectReference.TryGet(out NetworkObject networkObject, networkManager)) { return networkObject.GetNetworkBehaviourAtOrderIndex(networkBehaviourRef.m_NetworkBehaviourId); diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs index dc910440e0..121364f08f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs @@ -10,6 +10,7 @@ namespace Unity.Netcode public struct NetworkObjectReference : INetworkSerializable, IEquatable { private ulong m_NetworkObjectId; + private static ulong nullId = ulong.MaxValue; /// /// The of the referenced . @@ -24,13 +25,13 @@ public ulong NetworkObjectId /// Creates a new instance of the struct. /// /// The to reference. - /// /// public NetworkObjectReference(NetworkObject networkObject) { if (networkObject == null) { - throw new ArgumentNullException(nameof(networkObject)); + m_NetworkObjectId = nullId; + return; } if (networkObject.IsSpawned == false) @@ -45,16 +46,20 @@ public NetworkObjectReference(NetworkObject networkObject) /// Creates a new instance of the struct. /// /// The GameObject from which the component will be referenced. - /// /// public NetworkObjectReference(GameObject gameObject) { if (gameObject == null) { - throw new ArgumentNullException(nameof(gameObject)); + m_NetworkObjectId = nullId; + return; } - var networkObject = gameObject.GetComponent() ?? throw new ArgumentException($"Cannot create {nameof(NetworkObjectReference)} from {nameof(GameObject)} without a {nameof(NetworkObject)} component."); + var networkObject = gameObject.GetComponent(); + if (networkObject == null) + { + 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,12 @@ public bool TryGet(out NetworkObject networkObject, NetworkManager networkManage /// /// The reference. /// The networkmanager. Uses to resolve if null. - /// The resolves . Returns null if the networkobject was not found + /// The resolved . Returns null if the networkobject was not found [MethodImpl(MethodImplOptions.AggressiveInlining)] private static NetworkObject Resolve(NetworkObjectReference networkObjectRef, NetworkManager networkManager = null) { + if (networkObjectRef.m_NetworkObjectId == nullId) + return null; networkManager = networkManager ?? NetworkManager.Singleton; networkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectRef.m_NetworkObjectId, out NetworkObject networkObject); From bfa630655854b42010fc9918d02b2511ce496477 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 5 Apr 2024 14:40:01 -0500 Subject: [PATCH 2/5] update adding changelog entry --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 0b27befc3e..ccae9d089d 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -32,6 +32,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Changed `NetworkObjectReference` and `NetworkBehaviourReference` can handle null references. (#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) From 3a7a2996581359c75046f77c6e861636691b73f2 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 5 Apr 2024 14:46:06 -0500 Subject: [PATCH 3/5] update and style Fixing some style issues along with property type issues. --- .../Serialization/NetworkBehaviourReference.cs | 8 +++++--- .../Runtime/Serialization/NetworkObjectReference.cs | 12 +++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs index f20e98375c..4bc978cd31 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs @@ -11,7 +11,7 @@ public struct NetworkBehaviourReference : INetworkSerializable, IEquatable /// Creates a new instance of the struct. @@ -23,7 +23,7 @@ public NetworkBehaviourReference(NetworkBehaviour networkBehaviour) if (networkBehaviour == null) { m_NetworkObjectReference = new NetworkObjectReference((NetworkObject)null); - m_NetworkBehaviourId = nullId; + m_NetworkBehaviourId = s_NullId; return; } if (networkBehaviour.NetworkObject == null) @@ -63,8 +63,10 @@ public bool TryGet(out T networkBehaviour, NetworkManager networkManager = nu [MethodImpl(MethodImplOptions.AggressiveInlining)] private static NetworkBehaviour GetInternal(NetworkBehaviourReference networkBehaviourRef, NetworkManager networkManager = null) { - if (m_NetworkBehaviourId == nullId) + if (networkBehaviourRef.m_NetworkBehaviourId == s_NullId) + { return null; + } if (networkBehaviourRef.m_NetworkObjectReference.TryGet(out NetworkObject networkObject, networkManager)) { return networkObject.GetNetworkBehaviourAtOrderIndex(networkBehaviourRef.m_NetworkBehaviourId); diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs index 121364f08f..c2f1ba46f1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs @@ -10,7 +10,7 @@ namespace Unity.Netcode public struct NetworkObjectReference : INetworkSerializable, IEquatable { private ulong m_NetworkObjectId; - private static ulong nullId = ulong.MaxValue; + private static ulong s_NullId = ulong.MaxValue; /// /// The of the referenced . @@ -30,7 +30,7 @@ public NetworkObjectReference(NetworkObject networkObject) { if (networkObject == null) { - m_NetworkObjectId = nullId; + m_NetworkObjectId = s_NullId; return; } @@ -51,12 +51,12 @@ public NetworkObjectReference(GameObject gameObject) { if (gameObject == null) { - m_NetworkObjectId = nullId; + m_NetworkObjectId = s_NullId; return; } var networkObject = gameObject.GetComponent(); - if (networkObject == null) + if (!networkObject) { throw new ArgumentException($"Cannot create {nameof(NetworkObjectReference)} from {nameof(GameObject)} without a {nameof(NetworkObject)} component."); } @@ -89,8 +89,10 @@ public bool TryGet(out NetworkObject networkObject, NetworkManager networkManage [MethodImpl(MethodImplOptions.AggressiveInlining)] private static NetworkObject Resolve(NetworkObjectReference networkObjectRef, NetworkManager networkManager = null) { - if (networkObjectRef.m_NetworkObjectId == nullId) + if (networkObjectRef.m_NetworkObjectId == s_NullId) + { return null; + } networkManager = networkManager ?? NetworkManager.Singleton; networkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectRef.m_NetworkObjectId, out NetworkObject networkObject); From 2ad30937fa9f40918767a8e90819dfd6bda55f0d Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 5 Apr 2024 15:13:08 -0500 Subject: [PATCH 4/5] test Adding some generalized testing to validate both NetworkBehaviourReference and NetworkObjectReference can be created using a null and serialized when null. --- .../NetworkBehaviourReferenceTests.cs | 48 +++++++++--- .../NetworkObjectReferenceTests.cs | 74 ++++++++++++++----- 2 files changed, 95 insertions(+), 27 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs index 472d6176ce..8cf3edcae3 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs @@ -17,6 +17,8 @@ public class NetworkBehaviourReferenceTests : IDisposable { private class TestNetworkBehaviour : NetworkBehaviour { + public static bool ReceivedRPC; + public NetworkVariable TestVariable = new NetworkVariable(); public TestNetworkBehaviour RpcReceivedBehaviour; @@ -25,6 +27,7 @@ private class TestNetworkBehaviour : NetworkBehaviour public void SendReferenceServerRpc(NetworkBehaviourReference value) { RpcReceivedBehaviour = (TestNetworkBehaviour)value; + ReceivedRPC = true; } } @@ -57,8 +60,43 @@ public IEnumerator TestRpc() 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(); + 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 @@ public IEnumerator TestRpcImplicitNetworkBehaviour() Assert.AreEqual(testNetworkBehaviour, testNetworkBehaviour.RpcReceivedBehaviour); } + [Test] public void TestNetworkVariable() { @@ -131,15 +170,6 @@ public void FailSerializeGameObjectWithoutNetworkObject() }); } - [Test] - public void FailSerializeNullBehaviour() - { - Assert.Throws(() => - { - NetworkBehaviourReference outReference = null; - }); - } - public void Dispose() { //Stop, shutdown, and destroy diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs index 8b29d17e62..ef459dea4e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs @@ -19,6 +19,7 @@ public class NetworkObjectReferenceTests : IDisposable { private class TestNetworkBehaviour : NetworkBehaviour { + public static bool ReceivedRPC; public NetworkVariable TestVariable = new NetworkVariable(); public NetworkObject RpcReceivedNetworkObject; @@ -28,6 +29,7 @@ private class TestNetworkBehaviour : NetworkBehaviour [ServerRpc] public void SendReferenceServerRpc(NetworkObjectReference value) { + ReceivedRPC = true; RpcReceivedGameObject = value; RpcReceivedNetworkObject = value; } @@ -150,6 +152,60 @@ public void TestTryGet() 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(); + 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 @@ public void FailSerializeGameObjectWithoutNetworkObject() }); } - [Test] - public void FailSerializeNullNetworkObject() - { - Assert.Throws(() => - { - NetworkObjectReference outReference = (NetworkObject)null; - }); - } - - [Test] - public void FailSerializeNullGameObject() - { - Assert.Throws(() => - { - NetworkObjectReference outReference = (GameObject)null; - }); - } - public void Dispose() { //Stop, shutdown, and destroy From f8e41450a7ac1c225c7e1ae854f6ffe670f0081e Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 5 Apr 2024 15:15:47 -0500 Subject: [PATCH 5/5] update Updating the changed description. --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index ccae9d089d..6ba2d17346 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -32,7 +32,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed -- Changed `NetworkObjectReference` and `NetworkBehaviourReference` can handle null references. (#2874) +- 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)