Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 6 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public struct NetworkBehaviourReference : INetworkSerializable, IEquatable<Netwo
{
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.
Expand All @@ -21,7 +22,9 @@ public NetworkBehaviourReference(NetworkBehaviour networkBehaviour)
{
if (networkBehaviour == null)
{
throw new ArgumentNullException(nameof(networkBehaviour));
m_NetworkObjectReference = new NetworkObjectReference((NetworkObject)null);
m_NetworkBehaviourId = s_NullId;
return;
}
if (networkBehaviour.NetworkObject == null)
{
Expand Down Expand Up @@ -60,6 +63,10 @@ public bool TryGet(out NetworkBehaviour networkBehaviour, NetworkManager network
[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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"/>.
Expand All @@ -24,13 +25,13 @@ public ulong NetworkObjectId
/// 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)
Expand All @@ -45,16 +46,20 @@ public NetworkObjectReference(NetworkObject networkObject)
/// 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.");
Expand All @@ -80,10 +85,14 @@ public bool TryGet(out NetworkObject networkObject, NetworkManager networkManage
/// </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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public class NetworkBehaviourReferenceTests : IDisposable
{
private class TestNetworkBehaviour : NetworkBehaviour
{
public static bool ReceivedRPC;

public NetworkVariable<NetworkBehaviourReference> TestVariable = new NetworkVariable<NetworkBehaviourReference>();

public TestNetworkBehaviour RpcReceivedBehaviour;
Expand All @@ -25,6 +27,7 @@ private class TestNetworkBehaviour : NetworkBehaviour
public void SendReferenceServerRpc(NetworkBehaviourReference value)
{
RpcReceivedBehaviour = (TestNetworkBehaviour)value;
ReceivedRPC = true;
}
}

Expand Down Expand Up @@ -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<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()
Expand Down Expand Up @@ -89,6 +127,7 @@ public IEnumerator TestRpcImplicitNetworkBehaviour()
Assert.AreEqual(testNetworkBehaviour, testNetworkBehaviour.RpcReceivedBehaviour);
}


[Test]
public void TestNetworkVariable()
{
Expand Down Expand Up @@ -131,15 +170,6 @@ public void FailSerializeGameObjectWithoutNetworkObject()
});
}

[Test]
public void FailSerializeNullBehaviour()
{
Assert.Throws<ArgumentNullException>(() =>
{
NetworkBehaviourReference outReference = null;
});
}

public void Dispose()
{
//Stop, shutdown, and destroy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class NetworkObjectReferenceTests : IDisposable
{
private class TestNetworkBehaviour : NetworkBehaviour
{
public static bool ReceivedRPC;
public NetworkVariable<NetworkObjectReference> TestVariable = new NetworkVariable<NetworkObjectReference>();

public NetworkObject RpcReceivedNetworkObject;
Expand All @@ -28,6 +29,7 @@ private class TestNetworkBehaviour : NetworkBehaviour
[ServerRpc]
public void SendReferenceServerRpc(NetworkObjectReference value)
{
ReceivedRPC = true;
RpcReceivedGameObject = value;
RpcReceivedNetworkObject = value;
}
Expand Down Expand Up @@ -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<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()
{
Expand Down Expand Up @@ -305,24 +361,6 @@ public void FailSerializeGameObjectWithoutNetworkObject()
});
}

[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
Expand Down