Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

ISerializationCallbackReceiver throwing exceptions

Discussion in 'Scripting' started by arkano22, Jun 1, 2015.

  1. arkano22

    arkano22

    Joined:
    Sep 20, 2012
    Posts:
    1,891
    Hi all!

    I´ve got this very simple implementation of a serializable hash set using ISerializationCallbackReceiver:

    Code (CSharp):
    1.  
    2. [Serializable]
    3. public class SerializableHashSet<T> : HashSet<T>,ISerializationCallbackReceiver
    4. {
    5.  
    6.     [SerializeField] private List<T> elements = new List<T>();
    7.    
    8.     // save the set to a list
    9.     public void OnBeforeSerialize()
    10.     {
    11.         elements.Clear();
    12.         elements.AddRange(this);
    13.     }
    14.    
    15.     // load set from list
    16.     public void OnAfterDeserialize()
    17.     {
    18.         Clear();
    19.         UnionWith(elements);
    20.     }
    21. }
    However this is constantly throwing "index out of range" when going into play mode:

    IndexOutOfRangeException: Array index is out of range.
    System.Collections.Generic.HashSet`1[T].Add (.T item)
    System.Collections.Generic.HashSet`1[T].UnionWith (IEnumerable`1 other)
    SerializableHashSet`1[T].OnAfterDeserialize () (at Assets/Model/Cloth/SerializableHashSet.cs:22)

    This seems to be a thread related issue, but I don`t have a clue what is exactly causing this. I only use one instance of this class in my whole project, which is instantiated in Awake(), and used in Update().

    I`ve also tried a serializable dictionary implemented in a similar way (two Lists instead of one, to hold keys and values respectively) and exceptions are being thrown too.

    any idea about what is going on?
     
    vexe likes this.
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    Seems like a bug.This seems to be happening internally in HashSet.UnionWith (in a call to Add), but the docs states that only a ArgumentNullException can be thrown by the UnionWith method.

    Have you tried iterating over the elements array, and adding the elements one by one?
     
  3. arkano22

    arkano22

    Joined:
    Sep 20, 2012
    Posts:
    1,891
    Hi Baste,

    Yes, in case of using a "foreach" instead, the same exception is thrown when reaching the "foreach" line.

    Even using the example dictionary implementation provided in the unity docs (http://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.OnBeforeSerialize.html) throws like crazy. In the docs they do warn you that modifying the object that is being serialized can confuse the serializer, and in the c# docs they also warn you that funky stuff can happen when accessing a hash set/dictionary from several threads at once, which i think is the cause of this error since unity serialization happens on a different thread.

    There must be a secret way of using ISerializationCallbackReceiver that i`m not aware of...o_O
     
  4. arkano22

    arkano22

    Joined:
    Sep 20, 2012
    Posts:
    1,891
    Well, after some more testing, ISerializationCallbackReceiver seems fundamentally broken to me. I´ve managed to reproduce this problem with a very simple setup, and it happens every single time.

    I´ve issued a bug, will report here any news on the subject.
     
    vexe likes this.
  5. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    Thanks for issuing a bug! - I've been using a serializable dictionary very similar to this one here
    What's weird is that I've used this method for a couple of days and it was working just fine, but now out of no where I started getting these IndexOutOfRangeExceptions in the foreach (at the call to MoveNext to be exact)

    Regarding dictionary, here's what MSDN says:

    So most likely we're dealing with a threading issue.

    I changed the relation to be composition instead of inheritance and it seems to be working (at least for now), i.e.

    Code (csharp):
    1.  
    2. public class SerializableDictionary<TK, TV> : ISerializationCallbackReceiver
    3. {
    4.     private Dictionary<TK, TV> _Dictionary;
    5.     [SerializeField] List<TK> _Keys;
    6.     [SerializeField] List<TV> _Values;
    7.  
    8.     // wrapper methods, serialization, etc...
    9. }
    10.  
    instead of:
    Code (csharp):
    1.  
    2. public class SerializableDictionary<TK, TV> : Dictionary<TK, TV>, ISerializationCallbackReceiver
    3. {
    4.     [SerializeField] List<TK> _Keys;
    5.     [SerializeField] List<TV> _Values;
    6.  
    7.     // serialization, etc...
    8. }
    9.  
    But it just feels stupid and redundant to have to write unnecessary wrappers etc. It should work if we inherit Dictionary directly.

    I also tried locking on a lock object or the dictionary itself (this) before iterating, didn't make any difference.
     
    Last edited: Jun 20, 2015
  6. Peez-Machine

    Peez-Machine

    Joined:
    Jul 30, 2013
    Posts:
    27
    Lately I've been having issues with ISerializationCallbackReceiver in 5.2. I'm essentially unable to modify any lists that are fields of classes that implement the interface -- the list will appear in the editor, but the size will always stay at 0, regardless of what I set it to (in some cases I think it would go to 1, but no further). This has been true for the following cases:

    1) Serializable versions of non-serializable classes. In fact, I copied the SerializeableHashset code posted by the OP and this issue occurred.

    2) Non-generic classes that inherit from classes mentioned in (1). For example an IntSerializableHashset : SerializableHashset<int> {}

    3) Classes that implement the interface in order to serialize a field, not "itself." I've included some code for such a class, which should simply be packing/unpacking a dictionary to/from lists when serializing/deserializing.

    Code (csharp):
    1.  
    2. [Serializable]
    3. public class DictHaver : ISerializationCallbackReceiver {
    4.     [SerializeField]
    5.     private List<int> keys; // = new List<int> ();  //initializing doesn't fix anything
    6.  
    7.     [SerializeField]
    8.     private List<string> values; // = new List<string> ();
    9.  
    10.     private Dictionary<int, string> d = new Dictionary<int, string>();
    11.  
    12.     public Dictionary<int,string> D { get { return this.d; } }
    13.  
    14.     public void OnBeforeSerialize(){
    15.         keys.Clear ();
    16.         values.Clear ();
    17.         foreach (KeyValuePair<int, string> pair in this.d) {
    18.             keys.Add (pair.Key);
    19.             values.Add (pair.Value);
    20.         }
    21.     }
    22.  
    23.     public void OnAfterDeserialize(){
    24.         this.d.Clear ();
    25.      
    26.         if (keys.Count != values.Count)
    27.             return;
    28.      
    29.         for (int i = 0; i< keys.Count; i++)
    30.             this.d.Add (keys [i], values [i]);
    31.     }
    32. }
    33.  

    I swear there was a time when such code worked as intended.

    However, I am able to create a List that implements ISerializationCallbackReceiver and stores its data in an underlying list field. Go figure.

    EDIT: Typically I use Vexe's VFW, and had the same issue with HashSets, but not Dictionaries. Looking through the Vexe code, I didn't find a FullSerializer for HashSet. Maybe that's the Vexe issue? (Just an aside, the rest of the post is about problems in vanilla Unity).
     
  7. Discipol

    Discipol

    Joined:
    May 6, 2015
    Posts:
    83
    Hi! Any update on this? I need a hashset<t> to be visible in the inspector and updated with SetDirty, which is what the serializable thing you added to it, should do, but it doesn't. can't see it in the inspector and when I go to Play, its cleared :| Please help senpai :D