Error sending custom type via RPC that IS REGISTERED

Options
Hello,

I know you get many questions here from people who are trying to send a custom type/class via RPC.
In the majority of these cases the person has not registered their custom class in CustomTypes.cs
I have done this, successfully.

However, my problem comes when trying to serialize a class that is derived from the custom class that has been registered.

For example, I have two classes
Condition, which has been registered in CustomTypes.cs
and Snare, which is derived from Condition.

I'm wondering if its possible to send the Snare class via RPC without needing to register it specifically.
I don't mind if the Snare class is stripped from its derivation in the process.

Thank you for your time!!

Adam

Comments

  • vadim
    Options
    Hi,

    If you don't mind also if received object type is Condition then just cast 'Snare' to Condition before sending. But if you need proper type info on receiving side, registering of Snare required.
  • akauper
    Options
    I'm not quite sure I know what you mean.

    Here is my CustomTypes.cs implementation of Condition:
    PhotonPeer.RegisterType(typeof(Condition), (byte)'C', SerializeCondition, DeserializeCondition); //This is within internal static void Register() at the top
    
        private static byte[] SerializeCondition(object customobject)
        {
            Condition c = (Condition)customobject;
            int index = 0;
    
            byte[] bytes = new byte[(3 * sizeof(int)) + (0 * sizeof(float))];
    
            Protocol.Serialize(c.Target.photonView.viewID, bytes, ref index);
            Protocol.Serialize(c.Owner.photonView.viewID, bytes, ref index);
            Protocol.Serialize((int)c.id, bytes, ref index);
    
            return bytes;
        }
        private static object DeserializeCondition(byte[] bytes)
        {
            int targetViewID;
            int ownerViewID;
            int conditionIDInt;
    
            int index = 0;
    
            Protocol.Deserialize(out targetViewID, bytes, ref index);
            Protocol.Deserialize(out ownerViewID, bytes, ref index);
            Protocol.Deserialize(out conditionIDInt, bytes, ref index);
    
            Champion target = GameManagement.GetChampion(targetViewID);
            Champion owner = GameManagement.GetChampion(ownerViewID);
            Condition.ID conditionID = (Condition.ID)conditionIDInt;
    
    
            Condition c = GameManagement.CreatePlayerCondition(target, owner, conditionID);
    
            return c;
        }
    


    The RPC I am sending looks something like this:
    [RPC]
    void SendCondition(Condition condition)
    {
         //do other stuff here
         photonView.RPC("SendCondition", PhotonTargets.OthersBuffered, condition);
    }
    

    I send this method a Snare (which is derived from Condition). Because of polymorphism it receives the Snare as a Condition and sends it.

    However, I still get an error about the inability to Serialize a Snare.

    To the best of my knowledge there is no way to completely strip a Derived class of its derivation in c#. Even when you cast a derived class to its base class (using as or any other method), it still holds its derived qualities.
  • vadim
    Options
    You are right. The object is still recognized as Snare and Photon does not check if class ancestors registered. You need Condition copy of original object to make this work.
    Then registering of Snare looks like better option which will cost you only one additional line with registration of ancestor's methods.
  • akauper
    Options
    The problem is that I have many derivations of the condition class (approx 50).

    I don't need the information about their derivation when serializing. It's created on both sides once the serialization is done.

    Any suggestions around this without having to create a registration method for each of my derivations of Condition?
  • akauper
    Options
    Is there any place I can add code to one of the Photon Scripts to check if a class is derived when serializing? Or is this an event that is raised on the server itself.
  • akauper
    Options
    A possible answer to this problem could be creating a deep clone of Snare
    For example:
    public static T DeepClone<T>(T obj)
    {
        using (var stream = new MemoryStream())
        {
            var formatter = new BinaryFormatter();
            formatter.Serialize(stream, obj);
            stream.Position = 0;
            return (T)formatter.Deserialize(stream);
        }
    }
    

    Add this method within the RPC method and call it if any of the parameters are typeof(Condition)
    In theory this should strip it of its derivation before its sent to the cloud for serialization
  • Tobias
    Options
    You do have a point there and I am considering changing the serialization but
    a) Changing serialization might break a lot of other user's cases and
    b) Looking at your Condition class, I would like you to consider refactoring your code.

    As far as I can see, your Conditions only send 2 viewIDs and an integer.
    When you send an RPC, you do that with a PhotonView on some GameObject. The object implicitly becomes the target of the RPC on all clients.
    Due to that, if you pick the target GO's photonView, you don't need to send the viewID anymore. It's taken care of by PUN.
    When you send some RPC, your ownerID automatically gets included in the message. If you could find the "owner" by the info who sent an RPC, then you also don't have to send that viewID either.
    This means you could reduce your message by one or two thirds. Which is good.

    I think in your case, instead of making the Condition a parameter of the RPC, I would crate a method Condition.CallRpc(). In it you can pick the target view, call:
    RPC("RemoteCondition", PhotonTargets.Others, (int)conditionID)
    Implement RemoteCondition(int i) or RemoteCondition(int i, PhotonMessageInfo info). From the info you can derive the sender. If that doesn't help, pass 2 integers: conditionId and ownerViewId.

    This will save a lot of traffic which will be good for the connection stability in the long run. And it doesn't make you wait for us to update anything.
    You also should also consider if you really need "OthersBuffered". The buffer must be sent to any new, joining player. All of the history you might be assembling. This might make clients with less bandwidth lag or they even break. If no other player ever joins late, then the buffering has no effect.
  • akauper
    Options
    Tobias,

    Thank you for the help and suggestions.

    You are correct, although the Condition class itself may need to hold two Champions, the RPC does not need to send them -- at least in most cases.

    Also, I will change the PhotonTargets mode from OthersBuffered to Others, as, after thinking about it, I realized I really didn't need them to be buffered.

    As for the serialization of polymorphic base and derived classes, it seems that any serialization breaks the polymorphic structure of a class.
    That's the idea behind using a Deep Clone on a class; whereas a class declared as Derived and stored in Base still holds its Derived qualities (it can be overidden, etc) a bit array does not hold this quality.
    Is there any way to keep this relationship when serializing into a bit array? Perhaps, if the custom class is derived, one or two header bytes could be put in place to store its derivations and its current state. This state of the Base class (if its holding a Derived class) could then be found through reflection before the RPC is sent.
    I know this option requires more calculation from the CPU and a larger size for each RPC sent. If there is a way to ONLY implement this method in this certain case then it may be viable.

    Please do excuse me if none of the above is possible or plausible. I'm just thinking out loud :)

    Thanks again,

    Adam
  • Tobias
    Options
    For us, it's not about the bitarray. We don't touch how things are arranged in memory.
    We use the Type of your class to store the fitting de/serialization methods. The Type of a derived class is different and thus we can't find it's ancestors simply.
    Even if we knew that a class is derived: If we send the base class instead the more specific class, we will also create the values as the derived class instead of what you sent.

    In those cases, it's easier and more flexible if you would serialize whatever you need to send. Add meta data as needed to re-create the values you wanted to send. If it's only the base, so be it.

    Anyways, I'm happy to read that you are OK with the feedback and info.
    I hope this works out fine for you.