Unity-Technologies / com.unity.netcode.gameobjects

Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.
MIT License
2.1k stars 430 forks source link

How about a NetworkBehaviourReference<T>? #2837

Open zachstronaut opened 3 months ago

zachstronaut commented 3 months ago

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);
    }
}
NoelStephensUnity commented 3 months ago

Interesting... neat approach! 👍 :godmode: