Skip to content

How about a NetworkBehaviourReference<T>?Β #2837

Open
@zachstronaut

Description

@zachstronaut

It would be amazing if RPC arguments could just actually be any NetworkBehaviour derivative class instead of only NetworkBehaviourReference so that the compiler could enforce type on those RPC arguments instead of boilerplate cast / TryGet code.

Short of that, doing an RPC with NetworkBehaviourReference<MyNetworkBehaviourClass> at least gives me peace of mind that the compiler can catch me trying to pass the RPC a reference to a NetworkBehaviour that isn't a MyNetworkBehaviourClass.

I did it like this:

using System;
using System.Runtime.CompilerServices;

namespace Unity.Netcode
{
    public struct NetworkBehaviourReference<T> : INetworkSerializable, IEquatable<NetworkBehaviourReference<T>>
        where T : NetworkBehaviour
    {
        private NetworkObjectReference m_NetworkObjectReference;
        private ushort m_NetworkBehaviourId;

        public NetworkBehaviourReference(NetworkBehaviour networkBehaviour)
        {
            if (networkBehaviour == null)
            {
                m_NetworkObjectReference = new NetworkObjectReference((NetworkObject)null);
                m_NetworkBehaviourId = 0;
                return;
                // throw new ArgumentNullException(nameof(networkBehaviour));
            }
            if (networkBehaviour.NetworkObject == null)
            {
                throw new ArgumentException($"Cannot create {nameof(NetworkBehaviourReference<T>)} from {nameof(NetworkBehaviour)} without a {nameof(NetworkObject)}.");
            }

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

        public bool TryGet(out T networkBehaviourSubtype, NetworkManager networkManager = null)
        {
            networkBehaviourSubtype = GetInternal(this, null) as T;
            return networkBehaviourSubtype != null;
        }

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

            return null;
        }

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

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

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

        public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
        {
            m_NetworkObjectReference.NetworkSerialize(serializer);
            serializer.SerializeValue(ref m_NetworkBehaviourId);
        }

        public static implicit operator T(NetworkBehaviourReference<T> networkBehaviourRef) => GetInternal(networkBehaviourRef) as T;

        public static implicit operator NetworkBehaviourReference<T>(T networkBehaviourSubtype) => new NetworkBehaviourReference<T>(networkBehaviourSubtype);
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    type:featureNew feature, request or improvement

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions