Search Unity

De-serialization changes types in list - help!

Discussion in 'Scripting' started by mhofer, Apr 19, 2018.

  1. mhofer

    mhofer

    Joined:
    Aug 30, 2017
    Posts:
    18
    I have 2 classes: BaseNode and DoNode (which derives from BaseNode).

    Now I have a List<BaseNode> where I store my DoNodes (and potentially other things that derive from BaseNode.

    All is well. Until a recompile happens and triggers Serialization/Deserialization. In OnBeforeSerialize it still reports the correct type, but after Deserialization I'm now left with only BaseNodes in my list. :(

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using System.Collections.Generic;
    4.  
    5. public class SerializationProblemWindow : EditorWindow, ISerializationCallbackReceiver {
    6.  
    7.     [MenuItem( "Window/SerializationProblemWindow" )]
    8.     public static void Init() {
    9.         EditorWindow.GetWindow( typeof( SerializationProblemWindow ), false, "SerializationProblemWindow" );
    10.     }
    11.  
    12.     public List<BaseNode> nodes = new List<BaseNode>();
    13.  
    14.     private void OnGUI() {
    15.         if (GUILayout.Button("Setup")) {
    16.             nodes.Clear();
    17.             nodes.Add(new DoNode());
    18.             nodes.Add(new DoNode());
    19.         }
    20.  
    21.         if (GUILayout.Button("Do")) {
    22.             for (int i = 0; i < nodes.Count; i++) {
    23.                 Debug.Log(nodes[i].GetType());
    24.                 nodes[i].Do();
    25.             }
    26.         }
    27.     }
    28.  
    29.  
    30.     public void OnBeforeSerialize() {
    31.         Debug.Log("OnBeforeSerialization");
    32.         for (int i = 0; i < nodes.Count; i++) {
    33.             Debug.Log("B:" + nodes[i].GetType());
    34.         }
    35.     }
    36.  
    37.     public void OnAfterDeserialize() {
    38.         Debug.Log("OnAfterDeserialize");
    39.         for (int i = 0; i < nodes.Count; i++) {
    40.             Debug.Log("A:" + nodes[i].GetType());
    41.         }
    42.     }
    43. }
    44.  
    45.  
    46. [System.Serializable]
    47. public class BaseNode {
    48.     public string name;
    49.  
    50.     public virtual void Do() {
    51.         Debug.Log("I am BaseNode");
    52.     }
    53. }
    54.  
    55. [System.Serializable]
    56. public class DoNode : BaseNode {
    57.     public override void Do() {
    58.         //Do important stuff here
    59.         Debug.Log("I am DoNode");
    60.     }
    61. }
    If I build the exact same thing as a MonoBehaviour and add it to a scene, it also reports everything as BaseNodes after Deserialization (recompile, scene reload), BUT the nodes themselves still know what they are! What's different and more importantly, how can I get this to work in the EditorWindow?
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    Unity's serialization engine does not serialize the information for the type of the field.

    Instead they reflect the type from the field/list information that the deserialized objects into.

    I believe the intent of this is to support deserialization compatibility. If the serialization engine tracked the type of the object, and you renamed the type, it would fail to deserialize in the future. At least, that's the benefit of their method... I'm trying to give them some benefit of the doubt as to why they chose the method they did for serialization.

    This is technically the same reason that interface serialization is not supported.

    So yeah... you can't have unity serialized fields/lists that supports a base class type and fill with sub classes. Unless they're UnityEngine.Object's specifically (components, scriptableobject, etc).

    ...

    I personally avoid doing this... BUT, if you really need the support. You can use the ISerializationCallbackReceiver. Take your list of base types, serialize it with something like the .net binary serializer, spit it out as a byte array, and allow unity serialize that byte array. Then on the inverse you'd take the byte array deserialize it with .net binary serializer, and fill your list back up.

    The downside to this is that it adds a lot of overhead the serialization/deserialization of this specific type.

    I wish that unity built in some sort of serializationcontext into their serialization callback receiver. Instead of requiring you to create even more fields. This way you could pass in tokens or something.

    But so is life.... ugh.
     
  3. mhofer

    mhofer

    Joined:
    Aug 30, 2017
    Posts:
    18
    I was afraid I might have to do something like that...The weird thing is that it works if I build the same thing as a MonoBehaviour. Even though it reports BaseNode after deserialization, the nodes are still do the correct thing when run.

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class SerializationProblem : MonoBehaviour, ISerializationCallbackReceiver {
    6.  
    7.     public List<BaseNode> nodes = new List<BaseNode>();
    8.  
    9.     private void OnValidate() {
    10.         nodes.Clear();
    11.         nodes.Add(new DoNode());
    12.         nodes.Add(new DoNode());
    13.     }
    14.  
    15.     public void OnBeforeSerialize() {
    16.         //Debug.Log("OnBeforeSerialization");
    17.         //for (int i = 0; i < nodes.Count; i++) {
    18.         //    Debug.Log(nodes[i].GetType());
    19.         //}
    20.     }
    21.  
    22.     public void OnAfterDeserialize() {
    23.         Debug.Log("OnAfterDeserialize");
    24.         for (int i = 0; i < nodes.Count; i++) {
    25.             Debug.Log("A:" + nodes[i].GetType());
    26.         }
    27.     }
    28.  
    29.  
    30.     void Start() {
    31.         for (int i = 0; i < nodes.Count; i++) {
    32.             Debug.Log(nodes[i].GetType());
    33.             nodes[i].Do();
    34.         }
    35.     }
    36. }
    37.  
    38.  
    39. [System.Serializable]
    40. public class BaseNode {
    41.     public string name;
    42.  
    43.     public virtual void Do() {
    44.         Debug.Log("I am BaseNode");
    45.     }
    46. }
    47.  
    48. [System.Serializable]
    49. public class DoNode : BaseNode {
    50.     public override void Do() {
    51.         Debug.Log("I am DoNode");
    52.     }
    53. }
    54.