Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unity Serialized Dictionary `Index Out Of Range` after 12 items

Update: This is a confirmed bug from Unity and it has been filed.

I have created a generic serializable dictionary and a specific one for int:Object.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;

/// <summary>
/// Is what it sounds like
/// </summary>
[System.Serializable]
public class SerializableDictionary<TKey,TValue> : Dictionary<TKey,TValue>, ISerializationCallbackReceiver 
{
  [SerializeField]
  protected List<TKey> keys = new List<TKey>();

  [SerializeField]
  protected List<TValue> values = new List<TValue>();

  //Save the dictionary to lists
  public void OnBeforeSerialize()
  {
    keys.Clear();
    values.Clear();
    foreach(KeyValuePair<TKey,TValue> pair in this)
    {
        keys.Add(pair.Key);
        values.Add(pair.Value);
    }
  }

  //Load the dictionary from lists
  public void OnAfterDeserialize()
  {
    this.Clear();

    if(keys.Count != values.Count)
    {
      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."));
    }

    for(int i = 0; i<keys.Count; i++)
    {
      this.Add(keys[i], values[i]);
    }
  }
}

[System.Serializable]
public class DictionaryOfStringAndInt : SerializableDictionary<string, int> { }

[System.Serializable]
public class DictionaryOfIntAndSerializableObject : SerializableDictionary<int, ScriptableObject> 
{
}

However the compiler goes crazy with the error

IndexOutOfRangeException: Array index is out of range. System.Collections.Generic.Dictionary`2+Enumerator[System.Int32,UnityEngine.ScriptableObject].MoveNext ()

For some reason this line

foreach(KeyValuePair<TKey,TValue> pair in this)

Throws that error but ONLY AFTER my DictionaryOfIntAndSerializableObject exceeds 12 objects. This baffles me completely.

Can you think of a reason why this DictionaryOfIntAndSerializableObject is throwing an out of range exception after 12 items?

Update This post has the same issue: http://forum.unity3d.com/threads/serializeable-dictionary-exceptions-after-code-change.319285/

Further Explanation

I have a database GameObject that holds all my model (type) data. And what I am doing is I parse CSV files into ScriptableObjects that I store inside the database GameObject. That way my database is compiled once at runtime and I can freely access it at both runtime and in the editor. This database object is what has the DictionaryOfIntAndSerializableObject objects. I have one for every table (so I have one for weapons, armor, etc).

Here is the base class of all my scriptable objects (that get stored as the value in the dictionary):

public class M_Card : ScriptableObject

{

  public E_CardTypes cardTypeID;

  public int rankID;

  public int rarityID;

  public int qualityID;

  public int editionID;

  public Sprite sprite;

 public M_Card()
 {
    cardTypeID                  = 0;
    rankID                      = 0;
    qualityID                   = 0;
    sprite = null;
 }

}

And here is the code where (after parsing the a row from the CSV) I put an instance of M_Card into the dictionary:

 private void LoadActionCards()
 {
    List<Dictionary<string, object>> listData = CSVReader.Read("Actions");

    for (var i = 0; i < listData.Count; i++)
    {
      Dictionary<string, object> data = listData[i];

      string name = (string)data["Name"];
      M_ActionCard card = ScriptableObjectCreator.createActionCard(name);

      int cardTypeID  = (int)data["CardTypeID"];
      card.cardTypeID = (E_CardTypes)cardTypeID;

      AssignActionCardValues(card, data);

      Database.ACTION_CARD_LIST.Add(card);

      // THIS IS WHERE I MAP THE TYPEID TO THE M_CARD OBJECT
      // THIS IS WHERE THE OBJECT IS ADDED TO THE DICTIONARY
      Database.ACTION_CARD_MAP[card.ID] = card;
    }
  }

So right now I have 4 tables of data (weapons, helmets, armor, classes). However, if any of those tables has more than 12 items I get that error 100% on the 13th item. I am still investigating but heres how keys are added to the dictionary.

Note that the M_HelmetCard is just the same as an M_ActionCard and they are both just empty subclasses of the M_Card.

Here are some pictures to add a little more proof. In the first I have all 16 helmets. The error appears enter image description here

Then here I make it so there is only 12 and the error is gone. enter image description here

Any list that reaches more than 12 errors out. The actual items don't matter (so its not like a specific 13th item is corrupted, 13 of the same items still cause this error).

Very baffled at the moment.

(I thought maybe the issue was I had a Sprite class that was getting serialized and was too much data, but when I got rid of it the problem persisted).

Update Here is a link to a sample project which has this issue (everything irrelevant removed). https://www.mediafire.com/?axxn3u823cm7c59

To produce the error: Click the database object in the heirachy and click Update Database button.

Then just hit play and you will see the error.

Then if you want to see how its the 13th item, open the Helmets CSV file (\Assets\Resources\ExcelData), and remove the 13th item. Then update the database again. Then hit play. The error will be gone!

This is a major WTF :?

like image 752
Aggressor Avatar asked Oct 19 '22 13:10

Aggressor


1 Answers

I stumbled upon the same issue. It seems like Unity's serialization sometimes break the internals of the serialized dictionary so that additional values can't be added.

In my case I got rid these errors by implementing the IDictionary interface on the SerializableDictionary and delegating it's functionality to a private Dictionary object that is untouched by Unity's serialization.

Here is my solution generated with the kind help of Visual Studio

using UnityEngine;
using System.Collections.Generic;
using System.Collections;

[System.Serializable]
public class SerializableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, ISerializationCallbackReceiver
{
    [SerializeField]
    private List<TKey> keys = new List<TKey>();

    [SerializeField]
    private List<TValue> values = new List<TValue>();

    private Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>();

    public ICollection<TKey> Keys
    {
        get
        {
            return ((IDictionary<TKey, TValue>)dictionary).Keys;
        }
    }

    public ICollection<TValue> Values
    {
        get
        {
            return ((IDictionary<TKey, TValue>)dictionary).Values;
        }
    }

    public int Count
    {
        get
        {
            return ((IDictionary<TKey, TValue>)dictionary).Count;
        }
    }

    public bool IsReadOnly
    {
        get
        {
            return ((IDictionary<TKey, TValue>)dictionary).IsReadOnly;
        }
    }

    public TValue this[TKey key]
    {
        get
        {
            return ((IDictionary<TKey, TValue>)dictionary)[key];
        }

        set
        {
            ((IDictionary<TKey, TValue>)dictionary)[key] = value;
        }
    }

    public void OnBeforeSerialize()
    {
        keys.Clear();
        values.Clear();
        foreach (KeyValuePair<TKey, TValue> pair in this)
        {
            keys.Add(pair.Key);
            values.Add(pair.Value);
        }
    }

    public void OnAfterDeserialize()
    {
        dictionary = new Dictionary<TKey, TValue>();

        if (keys.Count != values.Count)
        {
            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.", keys.Count, values.Count));
        }

        for (int i = 0; i < keys.Count; i++)
        {
            Add(keys[i], values[i]);
        }
    }

    public void Add(TKey key, TValue value)
    {
        ((IDictionary<TKey, TValue>)dictionary).Add(key, value);
    }

    public bool ContainsKey(TKey key)
    {
        return ((IDictionary<TKey, TValue>)dictionary).ContainsKey(key);
    }

    public bool Remove(TKey key)
    {
        return ((IDictionary<TKey, TValue>)dictionary).Remove(key);
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        return ((IDictionary<TKey, TValue>)dictionary).TryGetValue(key, out value);
    }

    public void Add(KeyValuePair<TKey, TValue> item)
    {
        ((IDictionary<TKey, TValue>)dictionary).Add(item);
    }

    public void Clear()
    {
        ((IDictionary<TKey, TValue>)dictionary).Clear();
    }

    public bool Contains(KeyValuePair<TKey, TValue> item)
    {
        return ((IDictionary<TKey, TValue>)dictionary).Contains(item);
    }

    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
    {
        ((IDictionary<TKey, TValue>)dictionary).CopyTo(array, arrayIndex);
    }

    public bool Remove(KeyValuePair<TKey, TValue> item)
    {
        return ((IDictionary<TKey, TValue>)dictionary).Remove(item);
    }

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
        return ((IDictionary<TKey, TValue>)dictionary).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IDictionary<TKey, TValue>)dictionary).GetEnumerator();
    }
}
like image 183
mediocrity Avatar answered Oct 21 '22 04:10

mediocrity