- Home /
Dictionary of Dictionaries - How do I store this and assemble it at runtime?
Unity doesn't save Dictionaries or Lists of arrays. Bah! :-O
I think I'm going to be making use of LINQ's ToDictionary, as described in this SO post. But those guys are not working with the serialization limitations of Unity. Here is the end results of what I want. How do I get there?
Dictionary<Material, Dictionary<ShaderType, Shader>> materialShaderSets;
enum ShaderType {Monoscopic, Red, Cyan, Green, Magenta}
The keys are this:
[SerializeField] List<Material> materials;
and the values are this (this can't be serialized it's just a representation):
List<Shader[]> shaderSets;
where every array in shaderSets is five shaders, matching with ShaderType. So while I want it to be a collection of type Dictionary at runtime, storing it as a bunch of arrays would probably be okay, and then I can use int -> enum conversion for the assembly. Please help me get there in the LINQiest, least typey, yet clear way possible! :-)
By the way, using enums as keys might cause performance issues. http://ayende.com/Blog/archive/2009/02/21/dictionaryltenumtgt-puzzler.aspx and more on google.
Answer by Statement · Mar 10, 2011 at 02:05 PM
See this example.
I didn't know if I should have used the two lists you posted. If you do, I can try rewrite this to use them instead.
using System.Collections.Generic; using System.Linq; using UnityEngine;
public class DictSer : MonoBehaviour { public enum ShaderType { Monoscopic, Red, Cyan, Green, Magenta }
[System.Serializable]
public class Storage
{
public Material material;
public ShaderType type;
public Shader shader;
}
public Dictionary<Material, Dictionary<ShaderType, Shader>> materialShaderSets;
public Storage[] array;
void Awake()
{
CreateSets();
DebugSets();
}
void CreateSets()
{
var query = from item in array
group item by item.material into materials
select new
{
material = materials.Key,
dictionary = materials.ToDictionary(m => m.type, m => m.shader)
};
materialShaderSets = query.ToDictionary(q => q.material, q => q.dictionary);
}
void DebugSets()
{
foreach (var item in materialShaderSets)
{
Debug.Log("Material: " + item.Key);
foreach (var shader in item.Value)
{
Debug.Log(" Type: " + shader.Key);
Debug.Log(" Shader: " + shader.Value);
}
}
}
}
I think you've posted plenty of information - thanks! But it seems to me that I should be able to store five times fewer storage objects, if the storage class contains one material, and an array of five shaders. If you ever feel like working with that, ins$$anonymous$$d, I'd love to see how you'd do it. Just add a comment so that I know that you did it.
I did post yet another answer, maybe not what you were looking for but I was writing that while you were busy writing this comment it seems :)
Answer by Statement · Mar 10, 2011 at 02:40 PM
If you are working with these lists:
[SerializeField] List<Material> materials;
[SerializeField] List<Shader[]> shaderSets;
And want to produce this Dictionary:
Dictionary<Material, Dictionary<ShaderType, Shader>> materialShaderSets;
Then I think this can build such a dictionary (untested, you'll have to try it out with your dataset):
var query = from i in Enumerable.Range(0, materials.Count) select new { material = materials[i], shaders = shaderSets[i].Select((s, j) => new { type = (ShaderType)j, shader = s }) };
materialShaderSets = query.ToDictionary(q => q.material, q => q.shaders.ToDictionary(s => s.type, s => s.shader));
Edit: A slightly more readable version of above code is found at pastebin.
It (should) work by the assumption that materials and shaderSets are of equal length, where each index of materials corresponds to the same index in shaderSets. Further, I use the index of each shader in a shaderSet to cast to enumeration type so I can build the nested dictionary.
Either case, it was a really fun problem :) More of this FTW!
Edit:
The default comparer for dictionary falls back to ObjectComparer which causes boxing for value types. So I provide a ShaderTypeComparer for you to use, to squeeze out that bit of overhead out of the figure.
class ShaderTypeComparer : IEqualityComparer<ShaderType> { public bool Equals(ShaderType x, ShaderType y) { return x == y; }
public int GetHashCode(ShaderType obj)
{
return obj.GetHashCode();
}
}
Then you can modify your dictionary creation code to:
materialShaderSets = query.ToDictionary(q => q.material,
q => q.shaders.ToDictionary(s => s.type, s => s.shader,
new ShaderTypeComparer()));
Small note - [SerializeField] List shaderSets; isn't serializable (Though that's possibly an error in the question? :>). But awesome answers at any rate :D
Yeah must be, I was a bit surprised about that too but I went along with it in case I had missed anything new since 3.3 :)
I meant for constructing List shaderSets to be part of the question, sorry.
An alternative to array-in-list would be to make the list flattened, coding indices 2d to 1d. So each shaderset is 5 indices long, every 5 indices is a new shader set.
If you want to learn Linq, here's a good page I use frequently when I feel lost 101 Linq Samples: http://msdn.microsoft.com/en-us/vcsharp/aa336746
Answer by Statement · Mar 10, 2011 at 04:16 PM
Yet another take on the question. This time, you'd be working exclusively with a dictionary and use Load/Save to handle support for serialization. I also got rid of a lot of redundant queries so it might look a bit neater. Here I also have support for the enum comparer.
using System; using System.Collections.Generic; using System.Linq; using UnityEngine;
public class JessyMaterials : MonoBehaviour { public enum ShaderType { Monoscopic, Red, Cyan, Green, Magenta, }
[SerializeField]
List<MaterialShaderData> _storage = new List<MaterialShaderData>();
[Serializable]
class MaterialShaderData
{
public Material Material;
public ShaderType Type;
public Shader Shader;
}
static ShaderTypeComparer _comparer = new ShaderTypeComparer();
class ShaderTypeComparer : IEqualityComparer<ShaderType>
{
public bool Equals(ShaderType x, ShaderType y)
{
return x == y;
}
public int GetHashCode(ShaderType obj)
{
return obj.GetHashCode();
}
}
public Dictionary<Material, Dictionary<ShaderType, Shader>>
materialShaderSets
= new Dictionary<Material, Dictionary<ShaderType, Shader>>();
public void LoadDictionary()
{
materialShaderSets = _storage
.GroupBy(data => data.Material)
.ToDictionary(
group => group.Key,
group => group.ToDictionary(
data => data.Type,
data => data.Shader,
_comparer
));
}
public void SaveDictionary()
{
_storage.Clear();
var flattened = from material in materialShaderSets
from shader in material.Value
select new MaterialShaderData()
{
Material = material.Key,
Type = shader.Key,
Shader = shader.Value
};
_storage.AddRange(flattened);
}
}
Hrm, you'd have to manage creation of the sub-dictionary yourself it seems. I forgot to handle that case. You could/should have public methods that handle adding shaders to a material and so on, creating a dictionary when its not there with the _comparer.
I think this is one of those times, where somebody is offering me great information, that requires some base knowledge that I don't yet have, in order for further comprehension to occur. I have a feeling I'm going to come back to this question, in some number of weeks/months, and learn a lot it happens.
I updated SaveDictionary to make use of LINQ and List.AddRange. But sure, feel free to contact me. I think, to understand LINQ you first should know Lambda Expressions. And to understand Lambda Expressions you should know Delegates. Basically, stuff like group => group.$$anonymous$$ey is just a delegate, that has an argument called group and returns the value of group.$$anonymous$$ey. Are you on IRC?
I haven't been in years; let me know if you feel a burning need to enlighten me about something, though, and I'll do it again. I'm totally comfortable with lambdas and delegates; I learned that stuff late last year and have been making extensive use of it. It's this "SQL-like syntax" (it's described this way but I've never used SQL) that I don't grok yet.
Well, I might be able to get on later tonight (european time). Otherwise maybe we can have a chat about it tomorrow. I am definitely up for discussing it if you want. I'm Statement on IRC as well, just drop me a pm there when I am on.
Your answer
Follow this Question
Related Questions
Nested Dictionary data lost after playmode 0 Answers
Serializing Dictionary with Vector3 keys 1 Answer
Array member treated as null even though it is not. 1 Answer
Is it possible to change the element name in an Array List? 1 Answer
Why is my C# array of objects fully populated when some of them should be null? 3 Answers