Skip to content

feat: add NetworkObject and NetworkBehaviour reference types #1173

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

Merged
merged 9 commits into from
Sep 16, 2021
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using System;
using System.Runtime.CompilerServices;

namespace Unity.Netcode
{
/// <summary>
/// A helper struct for serializing <see cref="NetworkBehaviour"/>s over the network. Can be used in RPCs and <see cref="NetworkVariable{T}"/>.
/// Note: network ids get recycled by the NetworkManager after a while. So a reference pointing to
/// </summary>
public struct NetworkBehaviourReference : INetworkSerializable, IEquatable<NetworkBehaviourReference>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrt:

The fix for this would be to cache the NetworkObject inside the reference after resolving it for the first time but this does not work with NetworkVariable as the struct would no longer be unmanaged.

I think this being a struct, value type and let's say documented with its proper usage etc would be good enough IMHO.
Maybe exposing internal m_NetworkBehaviourId over a public get-only ulong property would give more clues about this thing is actually working inside (+ maybe another TryGet API that accepts that ID too?).
To be fair, I'd like people to use this like: MyServerRpc(new NetworkBehaviourReference(MyNetworkBehaviour)); etc (you get the idea).

long story short, I wouldn't worry about it too much :P

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also what about super-generic GameObject one where we try to get NetworkObject or NetworkBehaviour component from it to serialize over the network? 👀

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can actually do MyServerRpc(MyNetworkBehaviour) and the implicit operator will make it work automagically as long as the parameter on the RPC function itself uses the reference.

Regarding the GameObject one. I think taking a NetworkBehaviour from it would be a bit too far fetched so I'm afraid people might get confused as to why they can send some GameObjectReferences and not others. NetworkObjectReference also has implicit operators from/to GameObject so you can pass an GameObject into an RPC with a NetworkObjectReference parameter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh this implicit operator, just saw this :)

public static implicit operator NetworkObjectReference(GameObject gameObject) => new NetworkObjectReference(gameObject);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with your second point too!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, what about adding a few other RPCs for these implicit operators into tests you implemented in this PR?
that way, they'll be tested + will be good sample code as well — what do you think?

{
private NetworkObjectReference m_NetworkObjectReference;
private ushort m_NetworkBehaviourId;

/// <summary>
/// Creates a new instance of the <see cref="NetworkBehaviourReference{T}"/> struct.
/// </summary>
/// <param name="networkBehaviour">The <see cref="NetworkBehaviour"/> to reference.</param>
/// <exception cref="ArgumentException"></exception>
public NetworkBehaviourReference(NetworkBehaviour networkBehaviour)
{
if (networkBehaviour == null)
{
throw new ArgumentNullException(nameof(networkBehaviour));
}
if (networkBehaviour.NetworkObject == null)
{
throw new ArgumentException($"Cannot create {nameof(NetworkBehaviourReference)} from {nameof(NetworkBehaviour)} without a {nameof(NetworkObject)}.");
}

m_NetworkObjectReference = networkBehaviour.NetworkObject;
m_NetworkBehaviourId = networkBehaviour.NetworkBehaviourId;
}

/// <summary>
/// Tries to get the <see cref="NetworkBehaviour"/> referenced by this reference.
/// </summary>
/// <param name="networkBehaviour">The <see cref="NetworkBehaviour"/> which was found. Null if the corresponding <see cref="NetworkObject"/> was not found.</param>
/// <param name="networkManager">The networkmanager. Uses <see cref="NetworkManager.Singleton"/> to resolve if null.</param>
/// <returns>True if the <see cref="NetworkBehaviour"/> was found; False if the <see cref="NetworkBehaviour"/> was not found. This can happen if the corresponding <see cref="NetworkObject"/> has not been spawned yet. you can try getting the reference at a later point in time.</returns>
public bool TryGet(out NetworkBehaviour networkBehaviour, NetworkManager networkManager = null)
{
networkBehaviour = GetInternal(this, null);
return networkBehaviour != null;
}

/// <summary>
/// Tries to get the <see cref="NetworkBehaviour"/> referenced by this reference.
/// </summary>
/// <param name="networkBehaviour">The <see cref="NetworkBehaviour"/> which was found. Null if the corresponding <see cref="NetworkObject"/> was not found.</param>
/// <param name="networkManager">The networkmanager. Uses <see cref="NetworkManager.Singleton"/> to resolve if null.</param>
/// <typeparam name="T">The type of the networkBehaviour for convenience.</typeparam>
/// <returns>True if the <see cref="NetworkBehaviour"/> was found; False if the <see cref="NetworkBehaviour"/> was not found. This can happen if the corresponding <see cref="NetworkObject"/> has not been spawned yet. you can try getting the reference at a later point in time.</returns>
public bool TryGet<T>(out T networkBehaviour, NetworkManager networkManager = null) where T : NetworkBehaviour
{
networkBehaviour = (T)GetInternal(this, null);
return networkBehaviour != null;
}

/// <inheritdoc/>
public void NetworkSerialize(NetworkSerializer serializer)
{
m_NetworkObjectReference.NetworkSerialize(serializer);
serializer.Serialize(ref m_NetworkBehaviourId);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static NetworkBehaviour GetInternal(NetworkBehaviourReference networkBehaviourRef, NetworkManager networkManager = null)
{
if (networkBehaviourRef.m_NetworkObjectReference.TryGet(out NetworkObject networkObject, networkManager))
{
return networkObject.GetNetworkBehaviourAtOrderIndex(networkBehaviourRef.m_NetworkBehaviourId);
}

return null;
}

/// <inheritdoc/>
public bool Equals(NetworkBehaviourReference other)
{
return m_NetworkObjectReference.Equals(other.m_NetworkObjectReference) && m_NetworkBehaviourId == other.m_NetworkBehaviourId;
}

/// <inheritdoc/>
public override bool Equals(object obj)
{
return obj is NetworkBehaviourReference other && Equals(other);
}

/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
return (m_NetworkObjectReference.GetHashCode() * 397) ^ m_NetworkBehaviourId.GetHashCode();
}
}

public static implicit operator NetworkBehaviour(NetworkBehaviourReference networkBehaviourRef) => GetInternal(networkBehaviourRef);

public static implicit operator NetworkBehaviourReference(NetworkBehaviour networkBehaviour) => new NetworkBehaviourReference(networkBehaviour);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using System;
using System.Runtime.CompilerServices;
using UnityEngine;

namespace Unity.Netcode
{
/// <summary>
/// A helper struct for serializing <see cref="NetworkObject"/>s over the network. Can be used in RPCs and <see cref="NetworkVariable{T}"/>.
/// </summary>
public struct NetworkObjectReference : INetworkSerializable, IEquatable<NetworkObjectReference>
{
private ulong m_NetworkObjectId;

/// <summary>
/// The <see cref="NetworkObject.NetworkObjectId"/> of the referenced <see cref="NetworkObject"/>.
/// </summary>
public ulong NetworkObjectId
{
get => m_NetworkObjectId;
internal set => m_NetworkObjectId = value;
}

/// <summary>
/// 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));
}

if (networkObject.IsSpawned == false)
{
throw new ArgumentException($"{nameof(NetworkObjectReference)} can only be created from spawned {nameof(NetworkObject)}s.");
}

m_NetworkObjectId = networkObject.NetworkObjectId;
}

/// <summary>
/// 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));
}

var networkObject = gameObject.GetComponent<NetworkObject>();

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.");
}

m_NetworkObjectId = networkObject.NetworkObjectId;
}

/// <summary>
/// Tries to get the <see cref="NetworkObject"/> referenced by this reference.
/// </summary>
/// <param name="networkObject">The <see cref="NetworkObject"/> which was found. Null if no object was found.</param>
/// <param name="networkManager">The networkmanager. Uses <see cref="NetworkManager.Singleton"/> to resolve if null.</param>
/// <returns>True if the <see cref="NetworkObject"/> was found; False if the <see cref="NetworkObject"/> was not found. This can happen if the <see cref="NetworkObject"/> has not been spawned yet. you can try getting the reference at a later point in time.</returns>
public bool TryGet(out NetworkObject networkObject, NetworkManager networkManager = null)
{
networkObject = Resolve(this, networkManager);
return networkObject != null;
}

/// <inheritdoc/>
public void NetworkSerialize(NetworkSerializer serializer)
{
serializer.Serialize(ref m_NetworkObjectId);
}

/// <summary>
/// Resolves the corresponding <see cref="NetworkObject"/> for this reference.
/// </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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static NetworkObject Resolve(NetworkObjectReference networkObjectRef, NetworkManager networkManager = null)
{
networkManager = networkManager != null ? networkManager : NetworkManager.Singleton;
networkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectRef.m_NetworkObjectId, out NetworkObject networkObject);

return networkObject;
}

/// <inheritdoc/>
public bool Equals(NetworkObjectReference other)
{
return m_NetworkObjectId == other.m_NetworkObjectId;
}

/// <inheritdoc/>
public override bool Equals(object obj)
{
return obj is NetworkObjectReference other && Equals(other);
}

/// <inheritdoc/>
public override int GetHashCode()
{
return m_NetworkObjectId.GetHashCode();
}

public static implicit operator NetworkObject(NetworkObjectReference networkObjectRef) => Resolve(networkObjectRef);

public static implicit operator NetworkObjectReference(NetworkObject networkObject) => new NetworkObjectReference(networkObject);

public static implicit operator GameObject(NetworkObjectReference networkObjectRef) => Resolve(networkObjectRef).gameObject;

public static implicit operator NetworkObjectReference(GameObject gameObject) => new NetworkObjectReference(gameObject);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading