Search Unity

  1. Calling all beginners! Join the FPS Beginners Mods Challenge until December 13.
    Dismiss Notice
  2. It's Cyber Week at the Asset Store!
    Dismiss Notice

Serializeable dictionary: exceptions after code change

Discussion in 'Scripting' started by neginfinity, Apr 16, 2015.

  1. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    6,445
    I'm using Unity 5.0.1 64bit on windows 7 64bit.

    I have this class:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. namespace CharacterCustomizer{
    6.     [System.Serializable]
    7.     public class SerialDictionary<Key, Value>: Dictionary<Key, Value>, ISerializationCallbackReceiver{
    8.         [SerializeField]
    9.         List<Key> serialKeys = new List<Key>();
    10.         [SerializeField]
    11.         List<Value> serialValues = new List<Value>();
    12.  
    13.         public void OnAfterDeserialize(){
    14.             base.Clear();
    15.             if(serialKeys.Count != serialValues.Count){
    16.                 throw new System.Exception(
    17.                     string.Format("there are {0} keys and {1} values after deserialization. Lengths do not match.")
    18.                     );
    19.             }
    20.             for (int i = 0; i < serialKeys.Count; i++){
    21.                 Add(serialKeys[i], serialValues[i]);
    22.             }
    23.         }
    24.  
    25.         public void OnBeforeSerialize(){
    26.             serialKeys.Clear();
    27.             serialValues.Clear();
    28.             var tmpKeys = base.Keys;
    29.             //
    30.             foreach(var curKey in tmpKeys){
    31.             //foreach(var keyVal in this){
    32.                 serialKeys.Add(curKey);
    33.                 serialValues.Add(base[curKey]);
    34.                 //serialKeys.Add(keyVal.Key);
    35.                 //serialValues.Add(keyVal.Value);
    36.             }
    37.         }
    38.     }
    39. }
    40.  
    Problem: when I change source code a bit in monodevelop AND unity editor reloads script, the class starts crashing furiously at this line:

    Code (CSharp):
    1.  
    2.         public void OnBeforeSerialize(){
    3. .....
    4.             foreach(var curKey in tmpKeys){//<== here
    5. ....
    6.  
    With this exception:
    And values start disappearing from dictionary. I've noticed similar problems with dictionaries in few other places.

    The behavior continues till I reload the scene.

    How do I fix this?
     
  2. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    6,445
    Problem solved. Apparently I had a dictionary with non-serializable data type somewhere, and it caused big mess during deserialization (which happens when unity reloads scene after source file change)
     
  3. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    638
    I'm having a similar issue. Can't seem to make sense of it. Could you explain a bit more how you fixed it? Got a feeling it's a threading issue...
     
    Aggressor and Patrascu like this.
  4. Aggressor

    Aggressor

    Joined:
    Aug 10, 2012
    Posts:
    43
    I have exact same issue. It also goes berserk on that line. Restarting the IDE stops the issue until it gets triggered again.

    Can you elaborate on your solution?
     
  5. Aggressor

    Aggressor

    Joined:
    Aug 10, 2012
    Posts:
    43
    Ok after really digging in I have come to a strange conclusion.

    So to be clear, I have created a seralizable dictionary like so:
    Code (CSharp):
    1. [System.Serializable]
    2. public class SerializableDictionary<TKey,TValue> : Dictionary<TKey,TValue>, ISerializationCallbackReceiver
    3. {
    4.   [SerializeField]
    5.   protected List<TKey> keys = new List<TKey>();
    6.  
    7.   [SerializeField]
    8.   protected List<TValue> values = new List<TValue>();
    9.  
    10.   //Save the dictionary to lists
    11.   public void OnBeforeSerialize()
    12.   {
    13.     keys.Clear();
    14.     values.Clear();
    15.     foreach(KeyValuePair<TKey,TValue> pair in this)
    16.     {
    17.         keys.Add(pair.Key);
    18.         values.Add(pair.Value);
    19.     }
    20.   }
    21.  
    22.   //Load the dictionary from lists
    23.   public void OnAfterDeserialize()
    24.   {
    25.     this.Clear();
    26.  
    27.     if(keys.Count != values.Count)
    28.     {
    29.       throw new System.Exception(string.Format("there are {0} keys and {1} values after deserialization. Make sure that both key and value types are serializable."));
    30.     }
    31.  
    32.     for(int i = 0; i<keys.Count; i++)
    33.     {
    34.       this.Add(keys[i], values[i]);
    35.     }
    36.   }
    37. }
    38.  
    39. [System.Serializable]
    40. public class DictionaryOfStringAndInt : SerializableDictionary<string, int> { }
    41.  
    42. [System.Serializable]
    43. public class DictionaryOfIntAndSerializableObject : SerializableDictionary<int, ScriptableObject>
    44. {
    45.     /// <summary>
    46.     /// Returns a random key in the dictionary
    47.     /// </summary>
    48.     /// <returns></returns>
    49.     public int RandomID
    50.     {
    51.       get
    52.       {
    53.         int value = keys[Random.Range(0, keys.Count)];
    54.         return value;
    55.       }
    56.     }
    57. }

    What was happening it seems is that any time my dictionary exceeded 12 items, it would throw this error. I suspect that by the 13th item, the serialization can't handle the memory of it and crashes. I don't know exactly why this is occuring but my research has found that after my serialized dictionary exceeds 12 items it crashes.
     
  6. Fattie

    Fattie

    Joined:
    Jul 5, 2012
    Posts:
    411
    it's a fascinating problem, but did you simply toss in some Debug.Log to determine how often it is running, if it is in some sort of recursive loop, etc ?

    Surely the objects you use for the ScriptableObject matter? (I just filled it with nulls and it was fine ...)
     
  7. Aggressor

    Aggressor

    Joined:
    Aug 10, 2012
    Posts:
    43
  8. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    638
  9. _watcher_

    _watcher_

    Joined:
    Nov 7, 2014
    Posts:
    151
    Hey @vexe,

    I've been following the http://forum.unity3d.com/threads/fi...acted-from-system-collections-generic.335797/ thread and using your solution for a while now.. the only thing i dislike, is that when it serializes, the string is pretty bloated big for ex:
    Code (CSharp):
    1. {"_Buckets":[-1,0,-1],"_HashCodes":[110182,0,0],"_Next":[-1,0,0],"_Count":1,"_Version":1,"_FreeList":-1,"_FreeCount":0,"_Keys":["one","",""]}
    Now that's fine, but i had to look for another solution (like serializing key/value pairs only). But here you (and others) mentioned the issue/bug with extending the Dictionary<Key, Value> and using ISerializationCallbackReceiver and i was wondering, if the problematic use-case has been fixed, because lookie here, found this on AssetStore today:
    Code (CSharp):
    1. https://assetstore.unity.com/packages/tools/integration/serializabledictionary-90477
    And it seems to use the same approach as outlined here to cause issues.

    Wonder if you noticed the asset and perhaps can give me your 2 cents on what you think about it. I'm gonna do some tests with it now, but as i was about to do just that, i noticed the code seems vaguely familiar (to this thread's code approach), so i thought to ask you also.

    Cheers!

    EDIT
    2018/05/26 - The package (for now) seems to work well enough for me. I only did simple tests required for my use-case, so i cant say its crash-free. The serialized output however is desirable.

    EDIT2
    Suddenly the error appeared with the abovementioned package after simply adding definition for another SerializableDictionary type (eg StringFloatDictionary). When i tried to open the inspector for the object containing script that uses the dictionary, the dictionary did not show in inspector, instead there were a lot of errors output, similiar to what i see in your error logs. I had to reimport the source codes for the SerializableDictionary from asset store, and add the required definitions again, and suddenly it works again. Dumbfounded why this happens, and unable to prevent it, ill resign to use this implementation as much as i would otherwise.
     
    Last edited: Jun 8, 2018