Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

Serialization of list of classes

Discussion in 'Scripting' started by numberkruncher, Jun 21, 2012.

  1. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    951
    I am working with a potentially large array of objects which can either point to a class instance or just be `null`.

    To my despair I have found that Unity is deserializing `null` references as objects. Is there a way to prevent this from happening, I want `null` to stay `null` to avoid wasting memory on a potentially large array.

    Code (csharp):
    1. [System.Serializable]
    2. public class MyCustomData {
    3.    // ...
    4. }
    5.  
    6. public class MyBehaviour : MonoBehaviour {
    7.  
    8.     public MyCustomData[] data;
    9.  
    10.     public void TestData() {
    11.         data = new MyCustomData[100];
    12.         data[0] = new MyCustomData();
    13.  
    14.         // Why can't these just be `null` - would be so much simpler!
    15.         data[1] = null;
    16.         data[2] = null;
    17.         // etc.
    18.     }
    19.  
    20. }
     
  2. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    951
    The other option is to make `MyCustomData` a `MonoBehaviour` itself and attach multiple instances to the game object associated with `MyBehaviour`. This approach concerns me a little because a `MonoBehaviour` is seems likely to be a lot more expensive than a standalone class.
     
  3. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    How?
     
  4. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    951
    In unity editor create game object and have an editor script assign an array that contains both object references and null references. Save scene, reload scene and 'null' seems to become class instances with default values assigned. Editor script cannot find any null references.
     
  5. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    Is there a problem with having MyCustomData derive from ScriptableObject?
     
  6. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    951
    How would that work? Would it still be possible to save game object as a prefab?
     
  7. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    MyBehaviour, yes. MyCustomData : ScriptableObject instances could be saved as .asset's, like with procedural meshes, if you needed them in the Project window, and not just available through code. For the code above, just use ScriptableObject.CreateInstance<T>() instead of new. If you do try new, you'll be yelled at by the Console.
     
    Last edited: Jun 21, 2012
  8. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    951
    ".asset" files are not really an option for me because there would literally be 1000s per scene.

    I have been considering using an array of `struct`'s instead because my data is less than 16B, immutable and a value-type does make some sense. This would benefit from less memory because there would be no need to store references to the data plus none of the overhead associated with classes.

    Unfortunately there doesn't appear to be a way for Unity to serialize custom structs.

    At the moment I feel like I am hitting my head against a brick wall with Unity serialization...
     
  9. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    I don't understand where the problem is. MyCustomData instances couldn't have been assets before. They didn't derive from UnityEngine.Object.

    Maybe you just need to define what you meant by "game object" here:
    I may have already answered it, and you didn't understand me, or some other communication error happened. Probably best to just try it yourself and post with results.
     
    Last edited: Jun 21, 2012
  10. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    951
    TestObject: GameObject <--- optionally saved as prefab
    ---> MyBehaviour
    ----------> data: MyCustomData[]

    An array field "data" of MyCustomData objects are are getting persisted except `null` values are not being respected. This is currently working for both game objects that are in a scene and game objects that have been saved as prefabs.

    I have not been deriving from `UnityEngine.Object` because Unity does support serialization of custom classes.

    Somehow I need to tell Unity to retain `null` references, or just work with an array of structs, or find some other lightweight method to serialize this data.
     
    Last edited: Jun 22, 2012
  11. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    As I was saying, that shouldn't be a problem when MyCustomData derives from ScriptableObject. Have you not tried it yet?

    Code (csharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. [Serializable] public class MyCustomData : ScriptableObject {}
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class MyBehaviour : MonoBehaviour {
    4.     [SerializeField] MyCustomData[] data;   // Don't use a public field.
    5.    
    6.     void Reset() {
    7.         data = new MyCustomData[] {
    8.             ScriptableObject.CreateInstance<MyCustomData>(), null, null,
    9.             ScriptableObject.CreateInstance<MyCustomData>()
    10.         };
    11.     }
    12. }
    13.  
    Apparently you have, for "TestObject". Specifically, from GameObject. It can't be a prefab otherwise.
     
    Last edited: Jun 22, 2012
  12. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    951
    First mention of `SerializedObject` (an editor class), I think you meant `ScriptableObject`. ScriptableObject's have to be saved as asset files because otherwise the data is lost upon saving the parent game object to a prefab file. This was a problem that the developers of PlayMaker seem to have experienced (towards bottom):

    http://forum.unity3d.com/threads/56947-Saving-instances-of-Scriptable-Objects-to-prefabs

    Yes, "TestObject" stems from "UnityEngine.Object", but "MyCustomData" doesn't.

    I apologize if I am not explaining the problem very well, not really sure how else to explain. I appreciate your kind perseverance to help though!
     
  13. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,529
    Unfortunately I don't think there is an easy way to get around Unity's treatment of null serialised references. The best you can do is to run a pass over your array (in Awake(), probably) and check for instances that have all default values, and set them to null at that point.
     
  14. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    Oh, I did. That's why I edited it. Sorry, tired. I'm trying to wrap my mind around what you need to do. You either need to point to something in a scene, which can't be serialized in a prefab, or to something outside a scene, which has to be an asset. Otherwise, you're not serializing pointers, and that's where the default class serialization is fine.
     
  15. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,529
    No, numberkruncher is trying to use a third option: a [Serializable] class. Unity serialises them as a kind of nested property for the component, and they're still reference types at runtime, but due to the way Unity's serialisation works, those references aren't properly preserved at serialisation, resulting in the behaviour numberkruncher is seeing here.
     
  16. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    I understand all that and don't see why the "No" was necessary. I mentioned "default class serialization".
     
  17. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,529
    Oh, you were referring to serialisation of [Serializable] classes by that term? I didn't think you could be because this entire thread is about one of the ways in which it's not fine (i.e. it doesn't preserve null refs correctly).
     
  18. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    951
    This is an interesting workaround. I have added the "ExecuteInEditor" attribute to my `MonoBehaviour` and this is working most of the time. However, after entering "Play Mode" Unity replaces all of the `null` references again but "Awake" is not called for "Edit Mode" until I reload the scene.

    I have added an interface function for accessing the data which does the following (in addition to the awake logic - kept for runtime only)
    Code (csharp):
    1. MyCustomData GetData(int lookup) {
    2.     return (_data[ lookup ] != null  !_data[ lookup ].empty)
    3.         ? _data[ lookup ]
    4.         : null;
    5. }
    This could obviously be a lot better, but given the restrictions I think that this is probably the only solution...
     
  19. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    I said they're fine when you're not specifically thinking about serializing pointers to instances of the class. Unity-serialized non-ScriptableObject classes are good for data storage, which includes the members storing pointers, but they're not appropriate for being empty pointers that will store references at some point. If you want to reference objects in a scene, and have some references be null, ScriptableObjects do that. But if you want to reference data outside a scene, then no, it doesn't make any sense to use ScriptableObjects that aren't assets. A prefab is not an asset in itself, just a container for asset references.
     
  20. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,529
    @numberkruncher: Maybe try OnEnable instead of Awake - that should get called again when entering play mode.

    @Jessy: Ah I see. Are you sure that you can store references to in-scene objects into ScriptableObjects? I thought that, like prefabs, it wasn't safe/reliable to do that, and that you could only store references to in-scene objects into other components in the same scene. (I've been using string paths to reference in-scene objects in my prefabs/SOs).
     
  21. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    Sorry that what I said didn't come across clearly enough. I am definitely not saying that you can store references to in-scene objects from outside the scene (Asset folder / Project view). I am saying that ScriptableObjects can be considered scene data. They also can be assets available to prefabs for referencing. But not both.
     
  22. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,529
    How are ScriptableObjects "scene" data? I didn't think they could be stored inside scene - I thought they were storable in .asset files only. How're you storing them in your scenes?
     
  23. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    You just create them, don't store them as Assets, and reference them with any field. Until nothing references them anymore, they are persistent.
     
  24. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,529
    They're persisted into the scene file that references them? I thought that unless you saved them to an asset, they were destroyed when you quit Unity.
     
  25. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    You ask a lot of questions about how this works; don't you have Unity to test with? :-O
     
  26. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,529
    Fair point. I've tested it and you are indeed correct.

    Interesting. I suppose it's consistent with the behavior of e.g. saving procedurally generated meshes into the scene file too. If I then add the SO to an asset and save the scene, then delete the asset file, the SO is gone.

    So I guess each UnityEngine.Object must have a (possibly null) asset path, and when serializing, the ObjectReference serializer includes any object with a null path directly instead of as a reference. This is also consistent with the fact that calling AddObjectToAsset on an object that's already been AddObjectToAsset'd to a different path produces an error (even when the files in question don't actually exist on disk).
     
unityunity