SerializableTupleCollection

From Unify Community Wiki
(Difference between revisions)
Jump to: navigation, search
(Added SerializableTupleCollection)
 
(Description)
Line 7: Line 7:
 
Usage looks like this.
 
Usage looks like this.
  
[[File:example_screenshot.png]]
+
[[File:example_code.png]]
  
 
And you get an editor that looks like this.
 
And you get an editor that looks like this.

Revision as of 17:11, 28 February 2021

Author: Fredrik Ludvigsen (Steinbitglis)

Contents

Description

This script lets you use serializable dictionaries in Unity with no boilerplate per new type.

Usage looks like this.

Example code.png

And you get an editor that looks like this.

Example screenshot.png

All files as a zip file

File:SerializableTupleCollection.zip

Usage

Copy and paste all files into Assets. See Example.cs for usage.

C# - Example.cs

using UnityEngine;
 
public class Example : MonoBehaviour
{
    public SerializableTupleCollection<string, string>     names;
    public SerializableTupleCollection<string, GameObject> gameObjects;
    public SerializableTupleCollection<string, Color>      colors;
}

C# - SerializableTupleCollection.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
 
using UnityEngine;
 
[Serializable]
public struct SerializableTuple<TKey, TValue> {
    public TKey   Item1;
    public TValue Item2;
 
    public SerializableTuple (TKey item1, TValue item2) {
        Item1 = item1;
        Item2 = item2;
    }
}
 
public class SerializableTupleCollection {}
 
[Serializable]
public partial class SerializableTupleCollection<TKey, TValue> : SerializableTupleCollection, IEnumerable<SerializableTuple<TKey, TValue>> {
    [SerializeField]
    private List<SerializableTuple<TKey, TValue>> list = new List<SerializableTuple<TKey, TValue>>();
 
    public IEnumerator<SerializableTuple<TKey, TValue>> GetEnumerator () {
        return list.GetEnumerator();
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
 
    // As disconnected Dictionary (for quick lookups)
    public Dictionary<TKey, TValue> ToDictionary() => list.ToDictionary(element => element.Item1, element => element.Item2);
 
    public static SerializableTupleCollection<TKey, TValue> FromDictionary(IDictionary<TKey, TValue> dictionary) {
        var result = new SerializableTupleCollection<TKey, TValue>();
        result.list = dictionary.Select( kvp => new SerializableTuple<TKey, TValue>( kvp.Key, kvp.Value ) ).ToList();
        return result;
    }
}

C# - SerializableTupleCollection_Manipulator.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
 
using UnityEngine;
 
public partial class SerializableTupleCollection<TKey, TValue> {
 
    public  IDictionary<TKey, TValue> Manipulator => manipulator ?? (manipulator = new CollectionManipulator(this));
    private IDictionary<TKey, TValue> manipulator;
 
    private class CollectionManipulator : IDictionary<TKey, TValue> {
        SerializableTupleCollection<TKey, TValue> collection;
 
        public CollectionManipulator (SerializableTupleCollection<TKey, TValue> collection) {
            this.collection = collection;
        }
 
#region IDictionary<TKey, TValue>
        public TValue this[TKey key] {
            get {
                var index = GetIndex(key);
                if (index == -1)
                    throw new KeyNotFoundException($"Key not found: {key}");
                return collection.list[index].Item2;
 
            }
            set {
                var index = GetIndex(key);
                if (index != -1) {
                    var p = collection.list[index];
                    p.Item2 = value;
                    collection.list[index] = p;
                }
                else
                    collection.list.Add( new SerializableTuple<TKey, TValue>( key, value ) );
            }
        }
 
        public ICollection<TKey>   Keys   => collection.list.Select( tuple => tuple.Item1 ).ToArray();
        public ICollection<TValue> Values => collection.list.Select( tuple => tuple.Item2 ).ToArray();
 
        public void Add(TKey key, TValue value) {
            if (GetIndex(key) != -1)
                throw new ArgumentException("An element with the same key already exists in the dictionary.");
            collection.list.Add( new SerializableTuple<TKey, TValue> ( key, value ) );
        }
 
        public bool ContainsKey(TKey key) => GetIndex(key) != -1;
 
        public bool Remove (TKey key) {
            var index = GetIndex(key);
            var removed = index != -1;
            if (removed)
                collection.list.RemoveAt(index);
            return removed;
        }
 
        public bool TryGetValue (TKey key, out TValue value) {
            var index = GetIndex(key);
            var gotValue = index != -1;
            value = gotValue ? collection.list[index].Item2 : default;
            return gotValue;
        }
#endregion
 
 
#region ICollection <KeyValuePair<TKey, TValue>>
        public int  Count      => collection.list.Count;
        public bool IsReadOnly => false;
 
        public void Add (KeyValuePair<TKey, TValue> item) => Add( item.Key, item.Value );
 
        public void Clear    ()                                => collection.list.Clear();
        public bool Contains (KeyValuePair<TKey, TValue> item) => GetIndex(item.Key) != -1;
 
        public void CopyTo (KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
            var numKeys = collection.list.Count;
            if (array.Length - arrayIndex < numKeys)
                throw new ArgumentException();
            for (int i = 0; i < numKeys; i++, arrayIndex++) {
                var element = collection.list[i];
                array[arrayIndex] = new KeyValuePair<TKey, TValue>( element.Item1, element.Item2 );
            }
        }
 
        public bool Remove(KeyValuePair<TKey, TValue> item) => Remove(item.Key);
#endregion
 
 
#region IEnumerable <KeyValuePair<TKey, TValue>>
        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator () {
            return collection.list.Select(ToKeyValuePair).GetEnumerator();
 
            KeyValuePair<TKey, TValue> ToKeyValuePair (SerializableTuple<TKey, TValue> sTuple) {
                return new KeyValuePair<TKey, TValue>( sTuple.Item1, sTuple.Item2 );
            }
        }
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
#endregion
 
        private static EqualityComparer<TKey> equalityComparer = EqualityComparer<TKey>.Default;
 
        private int GetIndex (TKey key) {
            var list        = collection.list;
            var numElements = list.Count;
            for (int i = 0; i < numElements; i++)
                if (equalityComparer.Equals(list[i].Item1, key))
                    return i;
            return -1;
        }
    }
}

C# - SerializableTupleCollectionDrawer.cs

using UnityEngine;
 
using UnityEditor;
using UnityEditorInternal;
 
[CustomPropertyDrawer(typeof(SerializableTupleCollection), true)]
public class SerializableTupleCollectionDrawer : PropertyDrawer {
 
    private ReorderableList list;
 
    public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) {
        if (list == null) {
            var listProp = property.FindPropertyRelative("list");
            list = new ReorderableList(property.serializedObject, listProp, true, false, true, true);
            list.drawElementCallback = DrawListItems;
        }
 
        var firstLine = position;
        firstLine.height = EditorGUIUtility.singleLineHeight;
        EditorGUI.PropertyField(firstLine, property, label);
 
        if (property.isExpanded) {
            position.y += firstLine.height;
 
            var prevLabelWidth = EditorGUIUtility.labelWidth;
            EditorGUIUtility.labelWidth = 80;
 
            list.DoList(position);
 
            EditorGUIUtility.labelWidth = prevLabelWidth;
        }
    }
 
    void DrawListItems(Rect rect, int index, bool isActive, bool isFocused) {
        SerializedProperty element = list.serializedProperty.GetArrayElementAtIndex(index); // The element in the list
 
        var keyProp  = element.FindPropertyRelative("Item1");
        var contents = new GUIContent[] {GUIContent.none, new GUIContent (" => ")};
 
        EditorGUI.MultiPropertyField(rect, contents, keyProp, EditorGUI.BeginProperty(rect, new GUIContent($"Element {index}"), element));
 
        EditorGUI.EndProperty();
    }
 
    public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
        if (property.isExpanded) {
            var listProp = property.FindPropertyRelative("list");
            if (listProp.arraySize < 2)
                return EditorGUIUtility.singleLineHeight + 42f;
            else
                return EditorGUIUtility.singleLineHeight + 21f * (listProp.arraySize + 1);
        }
        else
            return EditorGUIUtility.singleLineHeight;
    }
}
Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox