com.unity.netcode.gameobjects icon indicating copy to clipboard operation
com.unity.netcode.gameobjects copied to clipboard

NetworkVariables and RPC calls are unable to serialize Interfaces

Open TheCaveOfWonders opened this issue 4 years ago • 7 comments

It seems that network variables and RPC methods don't work with Interfaces.

Network Variables: the following will not work, we will get a complaint about a missing parameterless constructor, even though we have one in the implementing classes.

private NetworkVariable<IOrder> _currentOrder;
public IOrder CurrentOrder
{
    get => _currentOrder.Value;
    private set => _currentOrder.Value = value;
}

The Interface IOrder inherits from INetworkSerializable and is implemented by the base class Order, and the latter is used as the base class for other "order like" classes. So I don't actually use the implementing class "Order" directly, I use its implementing classes that inherit from it.

I did notice that the network variable _currentOrder starts out NOT null, as in I never called _currentOrder = new NetworkVariable<IOrder>(), but it's as if Unity does that on its own during initialization, and then tries to serialize it, but probably doesn't know what implementation to use (since I didn't assign anything to it yet) and I'm guessing that causes the "missing parameterless constructor" error. I messed around a bit by overriding the NetworkStart method, and explicitly setting my network variable to null "_currentOrder = null", and then later assigning it to a new NetworkVariable object when I'm ready to use it, and that seemed to have fixed that error I was getting, but It seemed too hacky of a solution and probably not what I should be doing; also not sure if that will cause any side effects.

If we replace the interface IOrder with its implementing class, it will now work.

private NetworkVariable<Order> _currentOrder;
public IOrder CurrentOrder
{
    get => _currentOrder.Value;
    private set => _currentOrder.Value = (Order)value;
}

RPC methods: I was unable to find a workaround, it seems like we cannot pass an Interface as a parameter and have to pass the implementing class instead. the following will not work, our "IOrder order" parameter will be null, it will not serialize, but if instead we use the implementing class "Order", it will work. This is kinda limiting, I'd rather pass an interface and not an implementing class (as I have many).

[ServerRpc(RequireOwnership = false)]
public async void IssueOrderServerRpc(IOrder order, ServerRpcParams serverRpcParams = default)

Is this a bug/limitation or am I doing something wrong?

TheCaveOfWonders avatar May 02 '21 17:05 TheCaveOfWonders

For RPCs. have you tried deriving your interfaces from INetworkSerializable and implementing the void NetworkSerialize(NetworkSerializer serializer) method?

i.e. Your IOrder could be: interface IOrder:INetworkSerializable

With using just an interface you will most likely have to do serialization yourself, however if we had more information about what the IOrder interface was then it might help to provide an alternate answer.

NoelStephensUnity avatar May 03 '21 16:05 NoelStephensUnity

For RPCs. have you tried deriving your interfaces from INetworkSerializable and implementing the void NetworkSerialize(NetworkSerializer serializer) method?

i.e. Your IOrder could be: interface IOrder:INetworkSerializable

With using just an interface you will most likely have to do serialization yourself, however if we had more information about what the IOrder interface was then it might help to provide an alternate answer.

yes

The Interface IOrder inherits from INetworkSerializable and is implemented by the base class Order,

TheCaveOfWonders avatar May 03 '21 16:05 TheCaveOfWonders

IOrder

using MLAPI.Serialization;

namespace Shared.Interfaces
{
    public interface IOrder : INetworkSerializable
    {
        string OrderId { get; }
        ulong UnitId { get; }
    }
}

BaseOrder

using MLAPI.Serialization;
using Shared.Interfaces;
using System;

namespace Assets.Scripts.Shared.Order
{
    [Serializable]
    public class BaseOrder : IOrder
    {
        private string _orderId;
        public string OrderId { get => _orderId; set => _orderId = value; }
        private ulong _unitId;
        public ulong UnitId { get => _unitId; set => _unitId = value; }

        public BaseOrder() { } //for networkVariable

        public BaseOrder(string orderId, ulong unitId)
        {
            OrderId = orderId;
            UnitId = unitId;
        }

        public void NetworkSerialize(NetworkSerializer serializer)
        {
            serializer.Serialize(ref _orderId);
            serializer.Serialize(ref _unitId);
        }
    }
}

AbilityOrder --> this is the class i use (and others like it), the ones above are just base classes

using System;

namespace Assets.Scripts.Shared.Order
{
    [Serializable]
    public class AbilityOrder : BaseOrder
    {
        public AbilityOrder() { }
        public AbilityOrder(string AbilityId, ulong unitId) : base(AbilityId, unitId)
        {
        }
    }
}

TheCaveOfWonders avatar May 03 '21 16:05 TheCaveOfWonders

NetworkVariables and RPCs are strongly typed. They don't just support no interfaces, they also don't support base classes or object etc.

The reason for that is because when MLAPI serializes your object it does not include any type information. So the other side would't be able to reconstruct the type and deserialize it again.

LukeStampfli avatar May 03 '21 18:05 LukeStampfli

NetworkVariables and RPCs are strongly typed. They don't just support no interfaces, they also don't support base classes or object etc.

The reason for that is because when MLAPI serializes your object it does not include any type information. So the other side would't be able to reconstruct the type and deserialize it again.

ok thanks for the clarification, is this final or is it a temporary implementation and will change in the future?

TheCaveOfWonders avatar May 03 '21 18:05 TheCaveOfWonders

This is not final. I was just explaining how serialization in MLAPI currently works. If you need to send different types with the same parameter you could write a custom INetworkSerializable which first serializes a number indicating the type and then reconstructs an object of a specific type.

LukeStampfli avatar May 03 '21 20:05 LukeStampfli

This is feature request, not a bug.

ashwinimurt avatar Mar 30 '22 14:03 ashwinimurt