├── .gitattributes ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── help-request.md └── PULL_REQUEST_TEMPLATE │ └── pull_request.md ├── .gitignore ├── Editor.meta ├── Editor ├── UDictionaryDrawer.cs ├── UDictionaryDrawer.cs.meta ├── UDictionaryExtensions.cs ├── UDictionaryExtensions.cs.meta ├── UDictionaryReadKeyDrawer.cs ├── UDictionaryReadKeyDrawer.cs.meta ├── Unity.SerializedDictionary.Editor.asmdef └── Unity.SerializedDictionary.Editor.asmdef.meta ├── LICENSE ├── LICENSE.meta ├── README.md ├── README.md.meta ├── Runtime.meta ├── Runtime ├── CommonUDictionaries.cs ├── CommonUDictionaries.cs.meta ├── HashHelpers.cs ├── HashHelpers.cs.meta ├── UDictionary.cs ├── UDictionary.cs.meta ├── Unity.SerializedDictionary.asmdef └── Unity.SerializedDictionary.asmdef.meta ├── UnityDictionaryExamples.unitypackage ├── UnityDictionaryExamples.unitypackage.meta ├── package.json └── package.json.meta /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at tomasz.piowczyk@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute: 2 | You're free to contribute with us. Make an issue or pull request. 3 | - [x] Use a clear and descriptive title for requests to identify the suggestion. 4 | - [x] Use [markdown](https://help.github.com/articles/getting-started-with-writing-and-formatting-on-github/). 5 | - [x] Make sure if you won't duplicate request. 6 | 7 | ## Pull request 8 | - [x] Make sure your code follows the code style of this project. 9 | - [x] Make sure if it works and it's TESTED 10 | - [x] Describe what've you done and why, clearly and concisely. 11 | - [x] Let us know if you updated documentation or it needs update. 12 | - [x] Check types of changes. 13 | 14 | ## Bug report - Create a report to help us fix a bug 15 | - [x] **Make sure if this bug is strict related to this project** 16 | - [x] **Make sure if you problem is not reported yet** 17 | - [x] **Always make a clear and concise description** 18 | - [x] **Paste Error information** 19 | - [x] **Describe the bug** 20 | - [x] **Write your expected behavior** 21 | - [x] **Tell how to reproduce** 22 | - [x] **Fix suggestion** 23 | - [x] **Screenshots** 24 | - [x] **Additional context** 25 | - [x] **Evironment** 26 | 27 | ## Feature request - Suggest an idea 28 | - [x] **Make sure if your request isn't reported yet** 29 | - [x] **Always make a clear and concise description** 30 | - [x] **Is your feature request related to a problem? If so, describe it** 31 | - [x] **Have you considered any alternatives? If so, describe them** 32 | - [x] **Do you have any screenshots to show it visually? If so, send it** 33 | - [x] **How big is change?** 34 | 35 | ## Help request - You don't understand something in project and need help. 36 | - [x] **Make sure if this request doesn't exist yet** 37 | - [x] **Make sure if you read tutorial/documentation (if exsts)** 38 | - [x] **Make sure if this request is strict related to this project** 39 | - [x] **Describe you problem clear and concisely** 40 | - [x] **Describe what you've done so far (paste code)** 41 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [Prastiwar] 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us fix a bug 4 | 5 | --- 6 | 7 | - [x] I've read [cotributing file](https://github.com/Prastiwar/RepositoryTemplate/tree/master/.github/CONTRIBUTING.md). 8 | ### **Clear and concise description of bug** 9 | 10 | ### **Error information(log)** 11 | 12 | ### **How to reproduce** 13 | 14 | ### **Fix suggestion** 15 | 16 | ### **Screenshots** 17 | 18 | ### **Additional context** 19 | 20 | ### **Evironment** 21 | 22 | |OS|Processor|Graphic Card|RAM|Other| 23 | | --- | --- | --- | --- | --- | 24 | | Win 7 64-bit | Intel i7-7770 | GTX 1060 3GB | 16 GB | | 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea 4 | 5 | --- 6 | 7 | - [x] I've read [cotributing file](https://github.com/Prastiwar/RepositoryTemplate/tree/master/.github/CONTRIBUTING.md). 8 | ### **Clear and concise description of feature** 9 | 10 | ### **Have you considered any alternatives? If so, describe them** 11 | 12 | ### **Screenshots** 13 | 14 | ### **Additional context** 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/help-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Help request 3 | about: You don't understand something in project and need help. 4 | 5 | --- 6 | 7 | - [x] I've read [cotributing file](https://github.com/Prastiwar/RepositoryTemplate/tree/master/.github/CONTRIBUTING.md). 8 | ### **Clear and concise description of problem** 9 | 10 | ### **Describe what you've done so far (paste code** 11 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request.md: -------------------------------------------------------------------------------- 1 | - [x] I've read [cotributing file](https://github.com/Prastiwar/RepositoryTemplate/tree/master/.github/CONTRIBUTING.md). 2 | - [ ] Have updated documentation 3 | - [ ] It works and it's tested 4 | ### Describe what've you done and why, clearly and concisely (also fill checklist) 5 | 6 | ### Types of changes 7 | - [ ] Bug fix (non-breaking change which fixes an issue) 8 | - [ ] New feature (non-breaking change which adds functionality) 9 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Ll]ibrary/ 2 | [Tt]emp/ 3 | [Oo]bj/ 4 | [Bb]uild/ 5 | [Bb]uilds/ 6 | Assets/AssetStoreTools* 7 | 8 | # Visual Studio cache directory 9 | .vs/ 10 | 11 | # Autogenerated VS/MD/Consulo solution and project files 12 | ExportedObj/ 13 | .consulo/ 14 | *.csproj 15 | *.unityproj 16 | *.sln 17 | *.suo 18 | *.tmp 19 | *.user 20 | *.userprefs 21 | *.pidb 22 | *.booproj 23 | *.svd 24 | *.pdb 25 | *.opendb 26 | 27 | # Unity3D generated meta files 28 | *.pidb.meta 29 | *.pdb.meta 30 | 31 | # Unity3D Generated File On Crash Reports 32 | sysinfo.txt 33 | 34 | # Builds 35 | *.apk 36 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3f04f499f6e4b054398e3fa45ef09500 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/UDictionaryDrawer.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Authored by Tomasz Piowczyk 3 | * MIT LICENSE: https://github.com/Prastiwar/UnitySerializedDictionary/blob/master/LICENSE 4 | * Repository: https://github.com/Prastiwar/UnitySerializedDictionary 5 | */ 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Reflection; 10 | using Unity.Collections; 11 | using UnityEditorInternal; 12 | using UnityEngine; 13 | using UnityEngine.Collections.Generic; 14 | 15 | namespace UnityEditor.Collections.Generic 16 | { 17 | [CustomPropertyDrawer(typeof(UDictionaryBase), true)] 18 | public class UDictionaryDrawer : PropertyDrawer 19 | { 20 | private const string DuplicatedKeyErrorMessage = "You have duplicated keys, some changes can be lost!"; 21 | private const string KeyTypeErrorMessage = "The key type does not support serialization"; 22 | private const string ValueTypeErrorMessage = "The value type does not support serialization"; 23 | 24 | private bool isEnabled = false; 25 | private readonly float space = 17; 26 | private readonly GUIContent cachedContent = new GUIContent(); 27 | protected readonly BindingFlags privateInstanceFlags = BindingFlags.NonPublic | BindingFlags.Instance; 28 | 29 | private bool hasDuplicatedKey; 30 | private readonly Vector2 redBoxOffset = new Vector2(35, 0); 31 | protected readonly Vector2 redBoxSize = new Vector2(5, 20); 32 | protected Texture2D redBoxTexture; 33 | protected GUIStyle redBoxStyle; 34 | 35 | private float elementHeight; 36 | private bool foldoutRList; 37 | private bool isReadOnly; 38 | 39 | protected int SelectedIndex { get; private set; } 40 | protected ReorderableList RList { get; private set; } 41 | 42 | protected SerializedProperty KeysProperty { get; private set; } 43 | protected SerializedProperty ValuesProperty { get; private set; } 44 | 45 | protected bool IsDragging => (bool)RList.GetType().GetField("m_Dragging", privateInstanceFlags).GetValue(RList); 46 | 47 | public sealed override bool CanCacheInspectorGUI(SerializedProperty property) 48 | { 49 | if (!isEnabled) 50 | { 51 | OnEnable(property); 52 | } 53 | EditorPrefs.SetBool(property.name, foldoutRList); 54 | return base.CanCacheInspectorGUI(property); 55 | } 56 | 57 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) => foldoutRList ? (RList != null ? RList.GetHeight() : 0) + space : space; 58 | 59 | private void OnEnable(SerializedProperty property) 60 | { 61 | isEnabled = true; 62 | OnEnabled(property); 63 | } 64 | 65 | protected virtual void OnEnabled(SerializedProperty property) 66 | { 67 | foldoutRList = EditorPrefs.GetBool(property.name); 68 | isReadOnly = Attribute.IsDefined(fieldInfo, typeof(ReadOnlyAttribute)); 69 | InitializeList(property); 70 | InitializeRedBoxVariables(); 71 | } 72 | 73 | protected virtual void InitializeRedBoxVariables() 74 | { 75 | redBoxTexture = new Texture2D(1, 1); 76 | redBoxTexture.SetPixel(0, 0, Color.red); 77 | redBoxTexture.Apply(); 78 | 79 | redBoxStyle = new GUIStyle(GUI.skin.box); 80 | redBoxStyle.normal.background = redBoxTexture; 81 | } 82 | 83 | public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label) 84 | { 85 | if (!isEnabled) 86 | { 87 | // this shouldn't be here, but since CanCacheInspectorGUI is apparently not called 88 | // there is not currently way to call this just on enabled 89 | OnEnable(property); 90 | } 91 | foldoutRList = EditorGUI.Foldout(new Rect(rect.position, new Vector2(rect.size.x, space)), foldoutRList, label, true); 92 | if (KeysProperty == null) 93 | { 94 | DrawErrorMessage(rect, property.name.Length, KeyTypeErrorMessage); 95 | } 96 | else if (ValuesProperty == null) 97 | { 98 | DrawErrorMessage(rect, property.name.Length, ValueTypeErrorMessage); 99 | } 100 | else if (hasDuplicatedKey) 101 | { 102 | DrawErrorMessage(rect, property.name.Length, DuplicatedKeyErrorMessage); 103 | } 104 | 105 | if (foldoutRList && RList != null) 106 | { 107 | hasDuplicatedKey = false; 108 | rect.y += space; 109 | RList.DoList(rect); 110 | } 111 | } 112 | 113 | protected void InitializeList(SerializedProperty prop) 114 | { 115 | KeysProperty = prop.FindPropertyRelative("m_keys"); 116 | ValuesProperty = prop.FindPropertyRelative("m_values"); 117 | if (KeysProperty == null || ValuesProperty == null) 118 | { 119 | RList = new ReorderableList(new List(), typeof(object), false, true, false, false); 120 | } 121 | else 122 | { 123 | RList = CreateList(prop.serializedObject, KeysProperty); 124 | RList.onSelectCallback += OnSelect; 125 | } 126 | } 127 | 128 | protected virtual ReorderableList CreateList(SerializedObject serializedObj, SerializedProperty elements) => 129 | new ReorderableList(serializedObj, elements, !isReadOnly, true, !isReadOnly, !isReadOnly) { 130 | drawHeaderCallback = DrawHeader, 131 | onAddCallback = OnAdd, 132 | onRemoveCallback = OnRemove, 133 | onReorderCallback = OnReorder, 134 | elementHeightCallback = GetReorderableElementHeight, 135 | drawElementCallback = DrawElement, 136 | }; 137 | 138 | protected void DrawHeader(Rect rect) => EditorGUI.LabelField(rect, fieldInfo.Name); 139 | 140 | protected void OnAdd(ReorderableList list) 141 | { 142 | int index = KeysProperty.arraySize; 143 | KeysProperty.InsertArrayElementAtIndex(index); 144 | ValuesProperty.InsertArrayElementAtIndex(index); 145 | } 146 | 147 | protected void OnRemove(ReorderableList list) 148 | { 149 | KeysProperty.DeleteArrayElementAtIndex(SelectedIndex); 150 | ValuesProperty.DeleteArrayElementAtIndex(SelectedIndex); 151 | } 152 | 153 | protected void OnReorder(ReorderableList list) => ValuesProperty.MoveArrayElement(SelectedIndex, list.index); 154 | 155 | private void OnSelect(ReorderableList list) => SelectedIndex = list.index; 156 | 157 | protected float GetReorderableElementHeight(int index) 158 | { 159 | float keyHeight = EditorGUI.GetPropertyHeight(KeysProperty.GetArrayElementAtIndex(index)); 160 | float valueHeight = EditorGUI.GetPropertyHeight(ValuesProperty.GetArrayElementAtIndex(index)); 161 | float height = 8 + Math.Max(keyHeight, valueHeight); 162 | if (!IsDragging || (IsDragging && elementHeight < height)) 163 | { 164 | elementHeight = height; 165 | } 166 | return elementHeight; 167 | } 168 | 169 | protected virtual void DrawElement(Rect rect, int index, bool isActive, bool isFocused) 170 | { 171 | rect.position = new Vector2(rect.position.x + 10, rect.position.y); 172 | SerializedProperty key = KeysProperty.GetArrayElementAtIndex(index); 173 | SerializedProperty value = ValuesProperty.GetArrayElementAtIndex(index); 174 | float halfSizeX = rect.size.x / 2; 175 | float leftOffset = 100; 176 | float rightOffset = 58; 177 | Vector2 sizeKey = new Vector2(halfSizeX - leftOffset, rect.size.y); 178 | Vector2 sizeValue = new Vector2(halfSizeX + rightOffset, rect.size.y); 179 | Vector2 positionValue = rect.position + new Vector2(sizeKey.x + 25, 0); 180 | 181 | float oldWidth = EditorGUIUtility.labelWidth; 182 | EditorGUIUtility.labelWidth = 50; 183 | OnBeforeDrawProperties(); 184 | rect = DrawPropertiesForElement(new Rect(rect.position, sizeKey), new Rect(positionValue, sizeValue), key, value); 185 | OnAfterDrawProperties(); 186 | EditorGUIUtility.labelWidth = oldWidth; 187 | 188 | CheckRedBoxForElement(rect, key, index); 189 | } 190 | 191 | protected virtual Rect DrawPropertiesForElement(Rect keyRect, Rect valueRect, SerializedProperty keyProp, SerializedProperty valueProp) 192 | { 193 | EditorGUI.PropertyField(keyRect, keyProp, GUIContent(keyProp.type), true); 194 | EditorGUI.PropertyField(valueRect, valueProp, GUIContent(valueProp.type), true); 195 | return keyRect; 196 | } 197 | 198 | protected virtual void CheckRedBoxForElement(Rect rect, SerializedProperty keyProp, int index) 199 | { 200 | if (KeysProperty.HasAnyElementSameValue(keyProp, index)) 201 | { 202 | DrawRedBox(rect, redBoxSize, redBoxStyle, redBoxOffset); 203 | hasDuplicatedKey = true; 204 | } 205 | } 206 | 207 | protected void DrawErrorMessage(Rect position, int nameLength, string message) 208 | { 209 | Vector2 offsetByName = new Vector2(nameLength * 8.25f, 0); 210 | Vector2 size = new Vector2(position.size.x, space); 211 | EditorGUI.HelpBox(new Rect(position.position + offsetByName, size), message, MessageType.Error); 212 | } 213 | 214 | protected void DrawRedBox(Rect position, Vector2 size, GUIStyle style, Vector2 offset = new Vector2()) 215 | { 216 | Rect iconRect = new Rect(position.position - offset, size); 217 | GUI.Label(iconRect, UnityEngine.GUIContent.none, redBoxStyle); // hack for drawing red error box without flickering or losing focus 218 | } 219 | 220 | protected GUIContent GUIContent(string text) 221 | { 222 | cachedContent.text = text; 223 | return cachedContent; 224 | } 225 | 226 | 227 | private bool wasEnabled; 228 | protected virtual void OnBeforeDrawProperties() 229 | { 230 | wasEnabled = GUI.enabled; 231 | GUI.enabled = !isReadOnly; 232 | } 233 | 234 | protected virtual void OnAfterDrawProperties() => GUI.enabled = wasEnabled; 235 | } 236 | } -------------------------------------------------------------------------------- /Editor/UDictionaryDrawer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 663a4ba97c0970b4f8780ea60385efda 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/UDictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Authored by Tomasz Piowczyk 3 | * MIT LICENSE: https://github.com/Prastiwar/UnitySerializedDictionary/blob/master/LICENSE 4 | * Repository: https://github.com/Prastiwar/UnitySerializedDictionary 5 | */ 6 | 7 | using System; 8 | using System.Collections; 9 | using System.Reflection; 10 | 11 | namespace UnityEditor.Collections.Generic 12 | { 13 | public static class UDictionaryExtensions 14 | { 15 | public static readonly BindingFlags PublicOrNotInstance = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; 16 | 17 | public static bool HasAnyElementSameValue(this SerializedProperty array, SerializedProperty key1, int skipIndex) 18 | { 19 | int length = array.arraySize; 20 | for (int i = 0; i < length; i++) 21 | { 22 | if (i == skipIndex) 23 | { 24 | continue; 25 | } 26 | 27 | SerializedProperty key2 = array.GetArrayElementAtIndex(i); 28 | object key1Value = key1 != null ? key1.GetValue() : null; 29 | object key2Value = key2 != null ? key2.GetValue() : null; 30 | if (key1Value == null ? key2Value == null : key1Value.Equals(key2Value)) 31 | { 32 | return true; 33 | } 34 | } 35 | return false; 36 | } 37 | 38 | public static object GetValue(this object source, string name) 39 | { 40 | if (source == null) 41 | { 42 | return null; 43 | } 44 | 45 | Type type = source.GetType(); 46 | while (type != null) 47 | { 48 | FieldInfo f = type.GetField(name, PublicOrNotInstance); 49 | if (f != null) 50 | { 51 | return f.GetValue(source); 52 | } 53 | 54 | PropertyInfo p = type.GetProperty(name, PublicOrNotInstance | BindingFlags.IgnoreCase); 55 | if (p != null) 56 | { 57 | return p.GetValue(source, null); 58 | } 59 | type = type.BaseType; 60 | } 61 | return null; 62 | } 63 | 64 | public static object GetValue(this object source, string name, int index) 65 | { 66 | IEnumerable enumerable = GetValue(source, name) as IEnumerable; 67 | if (enumerable == null) 68 | { 69 | return null; 70 | } 71 | 72 | IEnumerator enm = enumerable.GetEnumerator(); 73 | for (int i = 0; i <= index; i++) 74 | { 75 | if (!enm.MoveNext()) 76 | { 77 | return null; 78 | } 79 | } 80 | return enm.Current; 81 | } 82 | 83 | public static object GetTargetObject(this SerializedProperty prop) 84 | { 85 | object targetObj = prop.serializedObject.targetObject; 86 | string[] elements = prop.propertyPath.Replace(".Array.data[", "[").Split('.'); 87 | int length = elements.Length; 88 | for (int i = 0; i < length; i++) 89 | { 90 | if (elements[i].Contains("[")) 91 | { 92 | string elementName = elements[i].Substring(0, elements[i].IndexOf("[")); 93 | int index = Convert.ToInt32(elements[i].Substring(elements[i].IndexOf("[")).Replace("[", "").Replace("]", "")); 94 | targetObj = GetValue(targetObj, elementName, index); 95 | } 96 | else 97 | { 98 | targetObj = GetValue(targetObj, elements[i]); 99 | } 100 | } 101 | return targetObj; 102 | } 103 | 104 | public static object GetValue(this SerializedProperty prop) 105 | { 106 | switch (prop.propertyType) 107 | { 108 | case SerializedPropertyType.Integer: 109 | return prop.intValue; 110 | case SerializedPropertyType.Float: 111 | return prop.floatValue; 112 | case SerializedPropertyType.String: 113 | return prop.stringValue; 114 | case SerializedPropertyType.Enum: 115 | return prop.enumValueIndex; 116 | case SerializedPropertyType.Boolean: 117 | return prop.boolValue; 118 | case SerializedPropertyType.Color: 119 | return prop.colorValue; 120 | case SerializedPropertyType.ObjectReference: 121 | return prop.objectReferenceValue; 122 | case SerializedPropertyType.Vector2: 123 | return prop.vector2Value; 124 | case SerializedPropertyType.Vector3: 125 | return prop.vector3Value; 126 | case SerializedPropertyType.Vector4: 127 | return prop.vector4Value; 128 | case SerializedPropertyType.Quaternion: 129 | return prop.quaternionValue; 130 | case SerializedPropertyType.Vector2Int: 131 | return prop.vector2IntValue; 132 | case SerializedPropertyType.Vector3Int: 133 | return prop.vector3IntValue; 134 | case SerializedPropertyType.ExposedReference: 135 | return prop.exposedReferenceValue; 136 | case SerializedPropertyType.ArraySize: 137 | return prop.arraySize; 138 | case SerializedPropertyType.Rect: 139 | return prop.rectValue; 140 | case SerializedPropertyType.RectInt: 141 | return prop.rectIntValue; 142 | case SerializedPropertyType.Bounds: 143 | return prop.boundsValue; 144 | case SerializedPropertyType.BoundsInt: 145 | return prop.boundsIntValue; 146 | case SerializedPropertyType.FixedBufferSize: 147 | return prop.fixedBufferSize; 148 | case SerializedPropertyType.AnimationCurve: 149 | return prop.animationCurveValue; 150 | //case SerializedPropertyType.Generic: 151 | // return key.; 152 | //case SerializedPropertyType.LayerMask: 153 | // return key.; 154 | //case SerializedPropertyType.Character: 155 | // return key.; 156 | //case SerializedPropertyType.Gradient: 157 | // return key.; 158 | default: 159 | break; 160 | } 161 | 162 | string typ = prop.type; 163 | if (typ == "double") 164 | { 165 | return prop.doubleValue; 166 | } 167 | else if (typ == "long") 168 | { 169 | return prop.longValue; 170 | } 171 | return prop.GetTargetObject(); 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /Editor/UDictionaryExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 99d229474177a8349b2d72953fa25eca 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/UDictionaryReadKeyDrawer.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Authored by Tomasz Piowczyk 3 | * MIT LICENSE: https://github.com/Prastiwar/UnitySerializedDictionary/blob/master/LICENSE 4 | * Repository: https://github.com/Prastiwar/UnitySerializedDictionary 5 | */ 6 | 7 | using UnityEngine; 8 | 9 | namespace UnityEditor.Collections.Generic 10 | { 11 | public class UDictionaryReadKeyDrawer : UDictionaryDrawer 12 | { 13 | protected override Rect DrawPropertiesForElement(Rect keyRect, Rect valueRect, SerializedProperty keyProp, SerializedProperty valueProp) 14 | { 15 | bool wasEnabled = GUI.enabled; 16 | GUI.enabled = false; 17 | EditorGUI.PropertyField(keyRect, keyProp, GUIContent(keyProp.type), true); 18 | GUI.enabled = wasEnabled; 19 | EditorGUI.PropertyField(valueRect, valueProp, GUIContent(valueProp.type), true); 20 | return keyRect; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Editor/UDictionaryReadKeyDrawer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b65b2e9da705c1042aa87919dd5a715a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Unity.SerializedDictionary.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Unity.SerializedDictionary.Editor", 3 | "references": [ 4 | "Unity.SerializedDictionary" 5 | ], 6 | "includePlatforms": [], 7 | "excludePlatforms": [], 8 | "allowUnsafeCode": false, 9 | "overrideReferences": false, 10 | "precompiledReferences": [], 11 | "autoReferenced": true, 12 | "defineConstraints": [], 13 | "versionDefines": [], 14 | "noEngineReferences": false 15 | } -------------------------------------------------------------------------------- /Editor/Unity.SerializedDictionary.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: eeda8ade51a8d1d47b50bf7b98b0594e 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Tomasz Piowczyk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ff123ad8155c6bc4f9a0167a0d66c750 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity-Serialized Dictionary 2 | 3 | ***Serialize Dictionary and see them visually in inspector*** 4 | 5 | [![GitHub last commit](https://img.shields.io/github/last-commit/Prastiwar/UnitySerializedDictionary.svg?label=Updated&style=flat-square&longCache=true)](https://github.com/Prastiwar/UnitySerializedDictionary/commits/master) 6 | [![license](https://img.shields.io/github/license/Prastiwar/UnitySerializedDictionary.svg?style=flat-square&longCache=true)](https://github.com/Prastiwar/UnitySerializedDictionary/blob/master/LICENSE) 7 | [![GitHub forks](https://img.shields.io/github/forks/Prastiwar/UnitySerializedDictionary.svg?style=social&label=Fork&longCache=true)](https://github.com/Prastiwar/UnitySerializedDictionary/fork) 8 | [![GitHub stars](https://img.shields.io/github/stars/Prastiwar/UnitySerializedDictionary.svg?style=social&label=★Star&longCache=true)](https://github.com/Prastiwar/UnitySerializedDictionary/stargazers) 9 | [![GitHub watchers](https://img.shields.io/github/watchers/Prastiwar/UnitySerializedDictionary.svg?style=social&labelWatcher&longCache=true)](https://github.com/Prastiwar/UnitySerializedDictionary/watchers) 10 | [![GitHub contributors](https://img.shields.io/github/contributors/Prastiwar/UnitySerializedDictionary.svg?style=social&longCache=true)](https://github.com/Prastiwar/UnitySerializedDictionary/contributors) 11 | 12 | ![GitHub repo size in bytes](https://img.shields.io/github/repo-size/Prastiwar/UnitySerializedDictionary.svg?style=flat-square&longCache=true) 13 | [![GitHub issues](https://img.shields.io/github/issues/Prastiwar/UnitySerializedDictionary.svg?style=flat-square&longCache=true)](https://github.com/Prastiwar/UnitySerializedDictionary/issues) 14 | [![GitHub closed issues](https://img.shields.io/github/issues-closed/Prastiwar/UnitySerializedDictionary.svg?style=flat-square&longCache=true)](https://github.com/Prastiwar/UnitySerializedDictionary/issues) 15 | [![GitHub pull requests](https://img.shields.io/github/issues-pr/Prastiwar/UnitySerializedDictionary.svg?style=flat-square&longCache=true)](https://github.com/Prastiwar/UnitySerializedDictionary/pulls) 16 | [![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed/Prastiwar/UnitySerializedDictionary.svg?style=flat-square&longCache=true)](https://github.com/Prastiwar/UnitySerializedDictionary/pulls) 17 | 18 | [![Made with Unity](https://img.shields.io/badge/Made%20with-Unity-000000.svg?longCache=true&style=for-the-badge&colorA=666677&colorB=222222)](https://unity3d.com/) 19 | 20 | ## Before start 21 | 22 | - [x] Make sure you have at least **Unity 2019.4** (Version 2017 was working fine before #3 PR, not sure if it will work now) 23 | 24 | 25 | ## Using 26 | 27 | Take a look at examples in package [THERE](https://github.com/Prastiwar/UnitySerializedDictionary/blob/master/UnityDictionaryExamples.unitypackage) 28 | 29 | ## Contributing 30 | 31 | You can freely contribute with me by reporting issues and making pull requests! 32 | Please read [CONTRIBUTING.md](https://github.com/Prastiwar/UnitySerializedDictionary/blob/master/.github/CONTRIBUTING.md) for details on contributing. 33 | 34 | ## Authors 35 | 36 | * ![Avatar](https://avatars3.githubusercontent.com/u/33370172?s=40&v=4) [**Tomasz Piowczyk**](https://github.com/Prastiwar) - *The Creator* 37 | See also the list of [contributors](https://github.com/Prastiwar/UnitySerializedDictionary/contributors) who participated in this project. 38 | 39 | ## License 40 | 41 | This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/Prastiwar/UnitySerializedDictionary/blob/master/LICENSE) file for details. 42 | 43 | ## Screenshots 44 | 45 | ![Screenshot](https://i.imgur.com/NayqCCy.png) 46 | ![Screenshot](https://i.imgur.com/kPFiORE.png) 47 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 41d6474b2d995b54bb3717ac0b44eb2c 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 43d70c02a2e2cc34cb12b41f42d63936 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/CommonUDictionaries.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UnityEngine.Collections.Generic 4 | { 5 | /// Common UDictionary wrappers 6 | 7 | // --------------------------------------------- String... --------------------------------------------- // 8 | 9 | [Serializable] 10 | public class UDictionaryStringString : UDictionary { } 11 | 12 | [Serializable] 13 | public class UDictionaryStringInt : UDictionary { } 14 | 15 | [Serializable] 16 | public class UDictionaryStringBool : UDictionary { } 17 | 18 | [Serializable] 19 | public class UDictionaryStringFloat : UDictionary { } 20 | 21 | [Serializable] 22 | public class UDictionaryStringDouble : UDictionary { } 23 | 24 | [Serializable] 25 | public class UDictionaryStringColor : UDictionary { } 26 | 27 | [Serializable] 28 | public class UDictionaryStringObject : UDictionary { } 29 | 30 | // --------------------------------------------- Int... --------------------------------------------- // 31 | 32 | [Serializable] 33 | public class UDictionaryIntInt : UDictionary { } 34 | 35 | [Serializable] 36 | public class UDictionaryIntBool : UDictionary { } 37 | 38 | [Serializable] 39 | public class UDictionaryIntString : UDictionary { } 40 | 41 | [Serializable] 42 | public class UDictionaryIntFloat : UDictionary { } 43 | 44 | [Serializable] 45 | public class UDictionaryIntDouble : UDictionary { } 46 | 47 | [Serializable] 48 | public class UDictionaryIntColor : UDictionary { } 49 | 50 | [Serializable] 51 | public class UDictionaryIntObject : UDictionary { } 52 | 53 | } -------------------------------------------------------------------------------- /Runtime/CommonUDictionaries.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: af24738c6bb9d5c40a9abbc01b8d80b3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/HashHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.ConstrainedExecution; 3 | 4 | namespace Unity.Collections 5 | { 6 | public static class HashHelpers 7 | { 8 | public const int HashPrime = 101; 9 | 10 | // Table of prime numbers to use as hash table sizes. 11 | // A typical resize algorithm would pick the smallest prime number in this array 12 | // that is larger than twice the previous capacity. 13 | // Suppose our Hashtable currently has capacity x and enough elements are added 14 | // such that a resize needs to occur. Resizing first computes 2x then finds the 15 | // first prime in the table greater than 2x, i.e. if primes are ordered 16 | // p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n. 17 | // Doubling is important for preserving the asymptotic complexity of the 18 | // hashtable operations such as add. Having a prime guarantees that double 19 | // hashing does not lead to infinite loops. IE, your hash function will be 20 | // h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime. 21 | public static readonly int[] primes = { 22 | 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, 23 | 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, 24 | 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, 25 | 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, 26 | 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369}; 27 | 28 | 29 | [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] 30 | public static bool IsPrime(int candidate) 31 | { 32 | if ((candidate & 1) != 0) 33 | { 34 | int limit = (int)Math.Sqrt(candidate); 35 | for (int divisor = 3; divisor <= limit; divisor += 2) 36 | { 37 | if ((candidate % divisor) == 0) 38 | { 39 | return false; 40 | } 41 | } 42 | return true; 43 | } 44 | return (candidate == 2); 45 | } 46 | 47 | [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] 48 | public static int GetPrime(int min) 49 | { 50 | if (min < 0) 51 | { 52 | throw new ArgumentException(); 53 | } 54 | 55 | for (int i = 0; i < primes.Length; i++) 56 | { 57 | int prime = primes[i]; 58 | if (prime >= min) 59 | { 60 | return prime; 61 | } 62 | } 63 | 64 | //outside of our predefined table. 65 | //compute the hard way. 66 | for (int i = (min | 1); i < int.MaxValue; i += 2) 67 | { 68 | if (IsPrime(i) && ((i - 1) % HashPrime != 0)) 69 | { 70 | return i; 71 | } 72 | } 73 | return min; 74 | } 75 | 76 | public static int GetMinPrime() => primes[0]; 77 | 78 | // Returns size of hashtable to grow to. 79 | public static int ExpandPrime(int oldSize) 80 | { 81 | int newSize = 2 * oldSize; 82 | 83 | // Allow the hashtables to grow to maximum possible size (~2G elements) before encoutering capacity overflow. 84 | // Note that this check works even when _items.Length overflowed thanks to the (uint) cast 85 | if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) 86 | { 87 | return MaxPrimeArrayLength; 88 | } 89 | return GetPrime(newSize); 90 | } 91 | 92 | 93 | // This is the maximum prime smaller than Array.MaxArrayLength 94 | public const int MaxPrimeArrayLength = 0x7FEFFFFD; 95 | 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Runtime/HashHelpers.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cc6893209386aa047bcb9bde720ba502 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/UDictionary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using Unity.Collections; 7 | 8 | // copy from https://referencesource.microsoft.com/#mscorlib/system/collections/generic/dictionary.cs 9 | // 修改以支持Unity序列化 10 | 11 | namespace UnityEngine.Collections.Generic 12 | { 13 | [Serializable] 14 | public abstract class UDictionaryBase { } 15 | 16 | [Serializable] 17 | public class UDictionary : UDictionaryBase, IDictionary, IDictionary, ISerializationCallbackReceiver 18 | { 19 | 20 | private struct Entry 21 | { 22 | public int hashCode; // Lower 31 bits of hash code, -1 if unused 23 | public int next; // Index of next entry, -1 if last 24 | public TKey key; // Key of entry 25 | public TValue value; // Value of entry 26 | } 27 | 28 | private int[] buckets; 29 | private Entry[] entries; 30 | private int count; 31 | private int version; 32 | private int freeList; 33 | private int freeCount; 34 | private IEqualityComparer comparer; 35 | private KeyCollection keys; 36 | private ValueCollection values; 37 | private Object _syncRoot; 38 | 39 | [SerializeField] 40 | private TKey[] m_keys; 41 | [SerializeField] 42 | private TValue[] m_values; 43 | 44 | 45 | public UDictionary() : this(0, null) { } 46 | 47 | public UDictionary(int capacity) : this(capacity, null) { } 48 | 49 | public UDictionary(IEqualityComparer comparer) : this(0, comparer) { } 50 | 51 | public UDictionary(int capacity, IEqualityComparer comparer) 52 | { 53 | if (capacity < 0) throw new ArgumentOutOfRangeException("capacity"); 54 | if (capacity > 0) Initialize(capacity); 55 | this.comparer = comparer ?? EqualityComparer.Default; 56 | } 57 | 58 | public UDictionary(IDictionary dictionary) : this(dictionary, null) { } 59 | 60 | public UDictionary(IDictionary dictionary, IEqualityComparer comparer) : 61 | this(dictionary != null ? dictionary.Count : 0, comparer) 62 | { 63 | 64 | if (dictionary == null) 65 | { 66 | throw new ArgumentNullException("dictionary"); 67 | } 68 | 69 | foreach (KeyValuePair pair in dictionary) 70 | { 71 | Add(pair.Key, pair.Value); 72 | } 73 | } 74 | 75 | public IEqualityComparer Comparer 76 | { 77 | get 78 | { 79 | return comparer; 80 | } 81 | } 82 | 83 | public int Count 84 | { 85 | get { return count - freeCount; } 86 | } 87 | 88 | public KeyCollection Keys 89 | { 90 | get 91 | { 92 | //Contract.Ensures(Contract.Result() != null); 93 | if (keys == null) keys = new KeyCollection(this); 94 | return keys; 95 | } 96 | } 97 | 98 | ICollection IDictionary.Keys 99 | { 100 | get 101 | { 102 | if (keys == null) keys = new KeyCollection(this); 103 | return keys; 104 | } 105 | } 106 | 107 | public ValueCollection Values 108 | { 109 | get 110 | { 111 | //Contract.Ensures(Contract.Result() != null); 112 | if (values == null) values = new ValueCollection(this); 113 | return values; 114 | } 115 | } 116 | 117 | ICollection IDictionary.Values 118 | { 119 | get 120 | { 121 | if (values == null) values = new ValueCollection(this); 122 | return values; 123 | } 124 | } 125 | 126 | public TValue this[TKey key] 127 | { 128 | get 129 | { 130 | int i = FindEntry(key); 131 | if (i >= 0) return entries[i].value; 132 | throw new KeyNotFoundException(); 133 | } 134 | set 135 | { 136 | Insert(key, value, false); 137 | } 138 | } 139 | 140 | void ISerializationCallbackReceiver.OnBeforeSerialize() 141 | { 142 | m_keys = Keys.ToArray(); 143 | m_values = Values.ToArray(); 144 | } 145 | 146 | void ISerializationCallbackReceiver.OnAfterDeserialize() 147 | { 148 | if (Count == 0 && m_keys != null && m_values != null) 149 | { 150 | int length = m_keys.Length; 151 | int valueLength = m_values.Length; 152 | if (length != valueLength) 153 | { 154 | Debug.LogError(string.Format("UDictionary data is broken! key length:{0} value length:{1} UDictionary type:{2}", length, valueLength, GetType())); 155 | } 156 | 157 | Clear(); 158 | for (int i = 0; i < length; i++) 159 | { 160 | this[m_keys[i]] = valueLength > i ? m_values[i] : default(TValue); 161 | } 162 | 163 | m_keys = null; 164 | m_values = null; 165 | } 166 | } 167 | 168 | public void Add(TKey key, TValue value) 169 | { 170 | Insert(key, value, true); 171 | } 172 | 173 | void ICollection>.Add(KeyValuePair keyValuePair) 174 | { 175 | Add(keyValuePair.Key, keyValuePair.Value); 176 | } 177 | 178 | bool ICollection>.Contains(KeyValuePair keyValuePair) 179 | { 180 | int i = FindEntry(keyValuePair.Key); 181 | if (i >= 0 && EqualityComparer.Default.Equals(entries[i].value, keyValuePair.Value)) 182 | { 183 | return true; 184 | } 185 | return false; 186 | } 187 | 188 | bool ICollection>.Remove(KeyValuePair keyValuePair) 189 | { 190 | int i = FindEntry(keyValuePair.Key); 191 | if (i >= 0 && EqualityComparer.Default.Equals(entries[i].value, keyValuePair.Value)) 192 | { 193 | Remove(keyValuePair.Key); 194 | return true; 195 | } 196 | return false; 197 | } 198 | 199 | public void Clear() 200 | { 201 | if (count > 0) 202 | { 203 | for (int i = 0; i < buckets.Length; i++) buckets[i] = -1; 204 | Array.Clear(entries, 0, count); 205 | freeList = -1; 206 | count = 0; 207 | freeCount = 0; 208 | version++; 209 | } 210 | } 211 | 212 | public bool ContainsKey(TKey key) 213 | { 214 | return FindEntry(key) >= 0; 215 | } 216 | 217 | public bool ContainsValue(TValue value) 218 | { 219 | if (value == null) 220 | { 221 | for (int i = 0; i < count; i++) 222 | { 223 | if (entries[i].hashCode >= 0 && entries[i].value == null) return true; 224 | } 225 | } 226 | else 227 | { 228 | EqualityComparer c = EqualityComparer.Default; 229 | for (int i = 0; i < count; i++) 230 | { 231 | if (entries[i].hashCode >= 0 && c.Equals(entries[i].value, value)) return true; 232 | } 233 | } 234 | return false; 235 | } 236 | 237 | private void CopyTo(KeyValuePair[] array, int index) 238 | { 239 | if (array == null) 240 | { 241 | throw new ArgumentNullException("array"); 242 | } 243 | 244 | if (index < 0 || index > array.Length) 245 | { 246 | throw new ArgumentOutOfRangeException("index"); 247 | } 248 | 249 | if (array.Length - index < Count) 250 | { 251 | throw new ArgumentException(); 252 | } 253 | 254 | int count = this.count; 255 | Entry[] entries = this.entries; 256 | for (int i = 0; i < count; i++) 257 | { 258 | if (entries[i].hashCode >= 0) 259 | { 260 | array[index++] = new KeyValuePair(entries[i].key, entries[i].value); 261 | } 262 | } 263 | } 264 | 265 | public Enumerator GetEnumerator() 266 | { 267 | return new Enumerator(this, Enumerator.KeyValuePair); 268 | } 269 | 270 | IEnumerator> IEnumerable>.GetEnumerator() 271 | { 272 | return new Enumerator(this, Enumerator.KeyValuePair); 273 | } 274 | 275 | private int FindEntry(TKey key) 276 | { 277 | if (key == null) 278 | { 279 | throw new ArgumentNullException("key"); 280 | } 281 | 282 | if (buckets != null) 283 | { 284 | int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; 285 | for (int i = buckets[hashCode % buckets.Length]; i >= 0; i = entries[i].next) 286 | { 287 | if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) return i; 288 | } 289 | } 290 | return -1; 291 | } 292 | 293 | private void Initialize(int capacity) 294 | { 295 | int size = HashHelpers.GetPrime(capacity); 296 | buckets = new int[size]; 297 | for (int i = 0; i < buckets.Length; i++) buckets[i] = -1; 298 | entries = new Entry[size]; 299 | freeList = -1; 300 | } 301 | 302 | private void Insert(TKey key, TValue value, bool add) 303 | { 304 | 305 | if (key == null) 306 | { 307 | throw new ArgumentNullException("key"); 308 | } 309 | 310 | if (buckets == null) Initialize(0); 311 | int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; 312 | int targetBucket = hashCode % buckets.Length; 313 | 314 | #if FEATURE_RANDOMIZED_STRING_HASHING 315 | int collisionCount = 0; 316 | #endif 317 | 318 | for (int i = buckets[targetBucket]; i >= 0; i = entries[i].next) 319 | { 320 | if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) 321 | { 322 | if (add) 323 | { 324 | throw new ArgumentException(); 325 | } 326 | entries[i].value = value; 327 | version++; 328 | return; 329 | } 330 | } 331 | int index; 332 | if (freeCount > 0) 333 | { 334 | index = freeList; 335 | freeList = entries[index].next; 336 | freeCount--; 337 | } 338 | else 339 | { 340 | if (count == entries.Length) 341 | { 342 | Resize(); 343 | targetBucket = hashCode % buckets.Length; 344 | } 345 | index = count; 346 | count++; 347 | } 348 | 349 | entries[index].hashCode = hashCode; 350 | entries[index].next = buckets[targetBucket]; 351 | entries[index].key = key; 352 | entries[index].value = value; 353 | buckets[targetBucket] = index; 354 | version++; 355 | } 356 | 357 | private void Resize() 358 | { 359 | Resize(HashHelpers.ExpandPrime(count), false); 360 | } 361 | 362 | private void Resize(int newSize, bool forceNewHashCodes) 363 | { 364 | //Contract.Assert(newSize >= entries.Length); 365 | int[] newBuckets = new int[newSize]; 366 | for (int i = 0; i < newBuckets.Length; i++) newBuckets[i] = -1; 367 | Entry[] newEntries = new Entry[newSize]; 368 | Array.Copy(entries, 0, newEntries, 0, count); 369 | if (forceNewHashCodes) 370 | { 371 | for (int i = 0; i < count; i++) 372 | { 373 | if (newEntries[i].hashCode != -1) 374 | { 375 | newEntries[i].hashCode = (comparer.GetHashCode(newEntries[i].key) & 0x7FFFFFFF); 376 | } 377 | } 378 | } 379 | for (int i = 0; i < count; i++) 380 | { 381 | if (newEntries[i].hashCode >= 0) 382 | { 383 | int bucket = newEntries[i].hashCode % newSize; 384 | newEntries[i].next = newBuckets[bucket]; 385 | newBuckets[bucket] = i; 386 | } 387 | } 388 | buckets = newBuckets; 389 | entries = newEntries; 390 | } 391 | 392 | public bool Remove(TKey key) 393 | { 394 | if (key == null) 395 | { 396 | throw new ArgumentNullException("key"); 397 | } 398 | 399 | if (buckets != null) 400 | { 401 | int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; 402 | int bucket = hashCode % buckets.Length; 403 | int last = -1; 404 | for (int i = buckets[bucket]; i >= 0; last = i, i = entries[i].next) 405 | { 406 | if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) 407 | { 408 | if (last < 0) 409 | { 410 | buckets[bucket] = entries[i].next; 411 | } 412 | else 413 | { 414 | entries[last].next = entries[i].next; 415 | } 416 | entries[i].hashCode = -1; 417 | entries[i].next = freeList; 418 | entries[i].key = default(TKey); 419 | entries[i].value = default(TValue); 420 | freeList = i; 421 | freeCount++; 422 | version++; 423 | return true; 424 | } 425 | } 426 | } 427 | return false; 428 | } 429 | 430 | public bool TryGetValue(TKey key, out TValue value) 431 | { 432 | int i = FindEntry(key); 433 | if (i >= 0) 434 | { 435 | value = entries[i].value; 436 | return true; 437 | } 438 | value = default(TValue); 439 | return false; 440 | } 441 | 442 | // This is a convenience method for the internal callers that were converted from using Hashtable. 443 | // Many were combining key doesn't exist and key exists but null value (for non-value types) checks. 444 | // This allows them to continue getting that behavior with minimal code delta. This is basically 445 | // TryGetValue without the out param 446 | internal TValue GetValueOrDefault(TKey key) 447 | { 448 | int i = FindEntry(key); 449 | if (i >= 0) 450 | { 451 | return entries[i].value; 452 | } 453 | return default(TValue); 454 | } 455 | 456 | bool ICollection>.IsReadOnly 457 | { 458 | get { return false; } 459 | } 460 | 461 | void ICollection>.CopyTo(KeyValuePair[] array, int index) 462 | { 463 | CopyTo(array, index); 464 | } 465 | 466 | void ICollection.CopyTo(Array array, int index) 467 | { 468 | if (array == null) 469 | { 470 | throw new ArgumentNullException("array"); 471 | } 472 | 473 | if (array.Rank != 1) 474 | { 475 | throw new ArgumentException(); 476 | } 477 | 478 | if (array.GetLowerBound(0) != 0) 479 | { 480 | throw new ArgumentException(); 481 | } 482 | 483 | if (index < 0 || index > array.Length) 484 | { 485 | throw new ArgumentOutOfRangeException("index"); 486 | } 487 | 488 | if (array.Length - index < Count) 489 | { 490 | throw new ArgumentException(); 491 | } 492 | 493 | KeyValuePair[] pairs = array as KeyValuePair[]; 494 | if (pairs != null) 495 | { 496 | CopyTo(pairs, index); 497 | } 498 | else if (array is DictionaryEntry[]) 499 | { 500 | DictionaryEntry[] dictEntryArray = array as DictionaryEntry[]; 501 | Entry[] entries = this.entries; 502 | for (int i = 0; i < count; i++) 503 | { 504 | if (entries[i].hashCode >= 0) 505 | { 506 | dictEntryArray[index++] = new DictionaryEntry(entries[i].key, entries[i].value); 507 | } 508 | } 509 | } 510 | else 511 | { 512 | object[] objects = array as object[]; 513 | if (objects == null) 514 | { 515 | throw new ArgumentException(); 516 | } 517 | 518 | try 519 | { 520 | int count = this.count; 521 | Entry[] entries = this.entries; 522 | for (int i = 0; i < count; i++) 523 | { 524 | if (entries[i].hashCode >= 0) 525 | { 526 | objects[index++] = new KeyValuePair(entries[i].key, entries[i].value); 527 | } 528 | } 529 | } 530 | catch (ArrayTypeMismatchException) 531 | { 532 | throw new ArgumentException(); 533 | } 534 | } 535 | } 536 | 537 | IEnumerator IEnumerable.GetEnumerator() 538 | { 539 | return new Enumerator(this, Enumerator.KeyValuePair); 540 | } 541 | 542 | bool ICollection.IsSynchronized 543 | { 544 | get { return false; } 545 | } 546 | 547 | object ICollection.SyncRoot 548 | { 549 | get 550 | { 551 | if (_syncRoot == null) 552 | { 553 | System.Threading.Interlocked.CompareExchange(ref _syncRoot, new Object(), null); 554 | } 555 | return _syncRoot; 556 | } 557 | } 558 | 559 | bool IDictionary.IsFixedSize 560 | { 561 | get { return false; } 562 | } 563 | 564 | bool IDictionary.IsReadOnly 565 | { 566 | get { return false; } 567 | } 568 | 569 | ICollection IDictionary.Keys 570 | { 571 | get { return (ICollection)Keys; } 572 | } 573 | 574 | ICollection IDictionary.Values 575 | { 576 | get { return (ICollection)Values; } 577 | } 578 | 579 | object IDictionary.this[object key] 580 | { 581 | get 582 | { 583 | if (IsCompatibleKey(key)) 584 | { 585 | int i = FindEntry((TKey)key); 586 | if (i >= 0) 587 | { 588 | return entries[i].value; 589 | } 590 | } 591 | return null; 592 | } 593 | set 594 | { 595 | if (key == null) 596 | { 597 | throw new ArgumentNullException("key"); 598 | } 599 | 600 | if (value == null && !(default(TValue) == null)) 601 | throw new ArgumentNullException("value"); 602 | 603 | try 604 | { 605 | TKey tempKey = (TKey)key; 606 | try 607 | { 608 | this[tempKey] = (TValue)value; 609 | } 610 | catch (InvalidCastException) 611 | { 612 | throw new ArgumentException("value"); 613 | } 614 | } 615 | catch (InvalidCastException) 616 | { 617 | throw new ArgumentException("key"); 618 | } 619 | } 620 | } 621 | 622 | private static bool IsCompatibleKey(object key) 623 | { 624 | if (key == null) 625 | { 626 | throw new ArgumentNullException("key"); 627 | } 628 | return (key is TKey); 629 | } 630 | 631 | void IDictionary.Add(object key, object value) 632 | { 633 | if (key == null) 634 | { 635 | throw new ArgumentNullException("key"); 636 | } 637 | 638 | if (value == null && !(default(TValue) == null)) 639 | throw new ArgumentNullException("value"); 640 | 641 | try 642 | { 643 | TKey tempKey = (TKey)key; 644 | 645 | try 646 | { 647 | Add(tempKey, (TValue)value); 648 | } 649 | catch (InvalidCastException) 650 | { 651 | throw new ArgumentException("value"); 652 | } 653 | } 654 | catch (InvalidCastException) 655 | { 656 | throw new ArgumentException("key"); 657 | } 658 | } 659 | 660 | bool IDictionary.Contains(object key) 661 | { 662 | if (IsCompatibleKey(key)) 663 | { 664 | return ContainsKey((TKey)key); 665 | } 666 | 667 | return false; 668 | } 669 | 670 | IDictionaryEnumerator IDictionary.GetEnumerator() 671 | { 672 | return new Enumerator(this, Enumerator.DictEntry); 673 | } 674 | 675 | void IDictionary.Remove(object key) 676 | { 677 | if (IsCompatibleKey(key)) 678 | { 679 | Remove((TKey)key); 680 | } 681 | } 682 | 683 | [Serializable] 684 | public struct Enumerator : IEnumerator>, 685 | IDictionaryEnumerator 686 | { 687 | private UDictionary dictionary; 688 | private int version; 689 | private int index; 690 | private KeyValuePair current; 691 | private int getEnumeratorRetType; // What should Enumerator.Current return? 692 | 693 | internal const int DictEntry = 1; 694 | internal const int KeyValuePair = 2; 695 | 696 | internal Enumerator(UDictionary dictionary, int getEnumeratorRetType) 697 | { 698 | this.dictionary = dictionary; 699 | version = dictionary.version; 700 | index = 0; 701 | this.getEnumeratorRetType = getEnumeratorRetType; 702 | current = new KeyValuePair(); 703 | } 704 | 705 | public bool MoveNext() 706 | { 707 | if (version != dictionary.version) 708 | { 709 | throw new InvalidOperationException(); 710 | } 711 | 712 | // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends. 713 | // dictionary.count+1 could be negative if dictionary.count is Int32.MaxValue 714 | while ((uint)index < (uint)dictionary.count) 715 | { 716 | if (dictionary.entries[index].hashCode >= 0) 717 | { 718 | current = new KeyValuePair(dictionary.entries[index].key, dictionary.entries[index].value); 719 | index++; 720 | return true; 721 | } 722 | index++; 723 | } 724 | 725 | index = dictionary.count + 1; 726 | current = new KeyValuePair(); 727 | return false; 728 | } 729 | 730 | public KeyValuePair Current 731 | { 732 | get { return current; } 733 | } 734 | 735 | public void Dispose() 736 | { 737 | } 738 | 739 | object IEnumerator.Current 740 | { 741 | get 742 | { 743 | if (index == 0 || (index == dictionary.count + 1)) 744 | { 745 | throw new InvalidOperationException(); 746 | } 747 | 748 | if (getEnumeratorRetType == DictEntry) 749 | { 750 | return new System.Collections.DictionaryEntry(current.Key, current.Value); 751 | } 752 | else 753 | { 754 | return new KeyValuePair(current.Key, current.Value); 755 | } 756 | } 757 | } 758 | 759 | void IEnumerator.Reset() 760 | { 761 | if (version != dictionary.version) 762 | { 763 | throw new InvalidOperationException(); 764 | } 765 | 766 | index = 0; 767 | current = new KeyValuePair(); 768 | } 769 | 770 | DictionaryEntry IDictionaryEnumerator.Entry 771 | { 772 | get 773 | { 774 | if (index == 0 || (index == dictionary.count + 1)) 775 | { 776 | throw new InvalidOperationException(); 777 | } 778 | 779 | return new DictionaryEntry(current.Key, current.Value); 780 | } 781 | } 782 | 783 | object IDictionaryEnumerator.Key 784 | { 785 | get 786 | { 787 | if (index == 0 || (index == dictionary.count + 1)) 788 | { 789 | throw new InvalidOperationException(); 790 | } 791 | 792 | return current.Key; 793 | } 794 | } 795 | 796 | object IDictionaryEnumerator.Value 797 | { 798 | get 799 | { 800 | if (index == 0 || (index == dictionary.count + 1)) 801 | { 802 | throw new InvalidOperationException(); 803 | } 804 | 805 | return current.Value; 806 | } 807 | } 808 | } 809 | 810 | [DebuggerDisplay("Count = {Count}")] 811 | [Serializable] 812 | public sealed class KeyCollection : ICollection, ICollection 813 | { 814 | private UDictionary dictionary; 815 | 816 | public KeyCollection(UDictionary dictionary) 817 | { 818 | if (dictionary == null) 819 | { 820 | throw new ArgumentNullException("dictionary"); 821 | } 822 | this.dictionary = dictionary; 823 | } 824 | 825 | public Enumerator GetEnumerator() 826 | { 827 | return new Enumerator(dictionary); 828 | } 829 | 830 | public void CopyTo(TKey[] array, int index) 831 | { 832 | if (array == null) 833 | { 834 | throw new ArgumentNullException("array"); 835 | } 836 | 837 | if (index < 0 || index > array.Length) 838 | { 839 | throw new ArgumentOutOfRangeException("index"); 840 | } 841 | 842 | if (array.Length - index < dictionary.Count) 843 | { 844 | throw new ArgumentException(); 845 | } 846 | 847 | int count = dictionary.count; 848 | Entry[] entries = dictionary.entries; 849 | for (int i = 0; i < count; i++) 850 | { 851 | if (entries[i].hashCode >= 0) array[index++] = entries[i].key; 852 | } 853 | } 854 | 855 | public int Count 856 | { 857 | get { return dictionary.Count; } 858 | } 859 | 860 | bool ICollection.IsReadOnly 861 | { 862 | get { return true; } 863 | } 864 | 865 | void ICollection.Add(TKey item) 866 | { 867 | throw new NotSupportedException(); 868 | } 869 | 870 | void ICollection.Clear() 871 | { 872 | throw new NotSupportedException(); 873 | } 874 | 875 | bool ICollection.Contains(TKey item) 876 | { 877 | return dictionary.ContainsKey(item); 878 | } 879 | 880 | bool ICollection.Remove(TKey item) 881 | { 882 | throw new NotSupportedException(); 883 | } 884 | 885 | IEnumerator IEnumerable.GetEnumerator() 886 | { 887 | return new Enumerator(dictionary); 888 | } 889 | 890 | IEnumerator IEnumerable.GetEnumerator() 891 | { 892 | return new Enumerator(dictionary); 893 | } 894 | 895 | void ICollection.CopyTo(Array array, int index) 896 | { 897 | if (array == null) 898 | { 899 | throw new ArgumentNullException("array"); 900 | } 901 | 902 | if (array.Rank != 1) 903 | { 904 | throw new ArgumentException(); 905 | } 906 | 907 | if (array.GetLowerBound(0) != 0) 908 | { 909 | throw new ArgumentException(); 910 | } 911 | 912 | if (index < 0 || index > array.Length) 913 | { 914 | throw new ArgumentOutOfRangeException("index"); 915 | } 916 | 917 | if (array.Length - index < dictionary.Count) 918 | { 919 | throw new ArgumentException(); 920 | } 921 | 922 | TKey[] keys = array as TKey[]; 923 | if (keys != null) 924 | { 925 | CopyTo(keys, index); 926 | } 927 | else 928 | { 929 | object[] objects = array as object[]; 930 | if (objects == null) 931 | { 932 | throw new ArgumentException(); 933 | } 934 | 935 | int count = dictionary.count; 936 | Entry[] entries = dictionary.entries; 937 | try 938 | { 939 | for (int i = 0; i < count; i++) 940 | { 941 | if (entries[i].hashCode >= 0) objects[index++] = entries[i].key; 942 | } 943 | } 944 | catch (ArrayTypeMismatchException) 945 | { 946 | throw new ArgumentException(); 947 | } 948 | } 949 | } 950 | 951 | bool ICollection.IsSynchronized 952 | { 953 | get { return false; } 954 | } 955 | 956 | System.Object ICollection.SyncRoot 957 | { 958 | get { return ((ICollection)dictionary).SyncRoot; } 959 | } 960 | 961 | [Serializable] 962 | public struct Enumerator : IEnumerator, System.Collections.IEnumerator 963 | { 964 | private UDictionary dictionary; 965 | private int index; 966 | private int version; 967 | private TKey currentKey; 968 | 969 | internal Enumerator(UDictionary dictionary) 970 | { 971 | this.dictionary = dictionary; 972 | version = dictionary.version; 973 | index = 0; 974 | currentKey = default(TKey); 975 | } 976 | 977 | public void Dispose() 978 | { 979 | } 980 | 981 | public bool MoveNext() 982 | { 983 | if (version != dictionary.version) 984 | { 985 | throw new InvalidOperationException(); 986 | } 987 | 988 | while ((uint)index < (uint)dictionary.count) 989 | { 990 | if (dictionary.entries[index].hashCode >= 0) 991 | { 992 | currentKey = dictionary.entries[index].key; 993 | index++; 994 | return true; 995 | } 996 | index++; 997 | } 998 | 999 | index = dictionary.count + 1; 1000 | currentKey = default(TKey); 1001 | return false; 1002 | } 1003 | 1004 | public TKey Current 1005 | { 1006 | get 1007 | { 1008 | return currentKey; 1009 | } 1010 | } 1011 | 1012 | System.Object System.Collections.IEnumerator.Current 1013 | { 1014 | get 1015 | { 1016 | if (index == 0 || (index == dictionary.count + 1)) 1017 | { 1018 | throw new InvalidOperationException(); 1019 | } 1020 | 1021 | return currentKey; 1022 | } 1023 | } 1024 | 1025 | void System.Collections.IEnumerator.Reset() 1026 | { 1027 | if (version != dictionary.version) 1028 | { 1029 | throw new InvalidOperationException(); 1030 | } 1031 | 1032 | index = 0; 1033 | currentKey = default(TKey); 1034 | } 1035 | } 1036 | } 1037 | 1038 | [DebuggerDisplay("Count = {Count}")] 1039 | [Serializable] 1040 | public sealed class ValueCollection : ICollection, ICollection 1041 | { 1042 | private UDictionary dictionary; 1043 | 1044 | public ValueCollection(UDictionary dictionary) 1045 | { 1046 | if (dictionary == null) 1047 | { 1048 | throw new ArgumentNullException("dictionary"); 1049 | } 1050 | this.dictionary = dictionary; 1051 | } 1052 | 1053 | public Enumerator GetEnumerator() 1054 | { 1055 | return new Enumerator(dictionary); 1056 | } 1057 | 1058 | public void CopyTo(TValue[] array, int index) 1059 | { 1060 | if (array == null) 1061 | { 1062 | throw new ArgumentNullException("array"); 1063 | } 1064 | 1065 | if (index < 0 || index > array.Length) 1066 | { 1067 | throw new ArgumentOutOfRangeException("index"); 1068 | } 1069 | 1070 | if (array.Length - index < dictionary.Count) 1071 | { 1072 | throw new ArgumentException(); 1073 | } 1074 | 1075 | int count = dictionary.count; 1076 | Entry[] entries = dictionary.entries; 1077 | for (int i = 0; i < count; i++) 1078 | { 1079 | if (entries[i].hashCode >= 0) array[index++] = entries[i].value; 1080 | } 1081 | } 1082 | 1083 | public int Count 1084 | { 1085 | get { return dictionary.Count; } 1086 | } 1087 | 1088 | bool ICollection.IsReadOnly 1089 | { 1090 | get { return true; } 1091 | } 1092 | 1093 | void ICollection.Add(TValue item) 1094 | { 1095 | throw new NotSupportedException(); 1096 | } 1097 | 1098 | bool ICollection.Remove(TValue item) 1099 | { 1100 | throw new NotSupportedException(); 1101 | } 1102 | 1103 | void ICollection.Clear() 1104 | { 1105 | throw new NotSupportedException(); 1106 | } 1107 | 1108 | bool ICollection.Contains(TValue item) 1109 | { 1110 | return dictionary.ContainsValue(item); 1111 | } 1112 | 1113 | IEnumerator IEnumerable.GetEnumerator() 1114 | { 1115 | return new Enumerator(dictionary); 1116 | } 1117 | 1118 | IEnumerator IEnumerable.GetEnumerator() 1119 | { 1120 | return new Enumerator(dictionary); 1121 | } 1122 | 1123 | void ICollection.CopyTo(Array array, int index) 1124 | { 1125 | if (array == null) 1126 | { 1127 | throw new ArgumentNullException("array"); 1128 | } 1129 | 1130 | if (array.Rank != 1) 1131 | { 1132 | throw new ArgumentException(); 1133 | } 1134 | 1135 | if (array.GetLowerBound(0) != 0) 1136 | { 1137 | throw new ArgumentException(); 1138 | } 1139 | 1140 | if (index < 0 || index > array.Length) 1141 | { 1142 | throw new ArgumentOutOfRangeException("index"); 1143 | } 1144 | 1145 | if (array.Length - index < dictionary.Count) 1146 | throw new ArgumentException(); 1147 | 1148 | TValue[] values = array as TValue[]; 1149 | if (values != null) 1150 | { 1151 | CopyTo(values, index); 1152 | } 1153 | else 1154 | { 1155 | object[] objects = array as object[]; 1156 | if (objects == null) 1157 | { 1158 | throw new ArgumentException(); 1159 | } 1160 | 1161 | int count = dictionary.count; 1162 | Entry[] entries = dictionary.entries; 1163 | try 1164 | { 1165 | for (int i = 0; i < count; i++) 1166 | { 1167 | if (entries[i].hashCode >= 0) objects[index++] = entries[i].value; 1168 | } 1169 | } 1170 | catch (ArrayTypeMismatchException) 1171 | { 1172 | throw new ArgumentException(); 1173 | } 1174 | } 1175 | } 1176 | 1177 | bool ICollection.IsSynchronized 1178 | { 1179 | get { return false; } 1180 | } 1181 | 1182 | System.Object ICollection.SyncRoot 1183 | { 1184 | get { return ((ICollection)dictionary).SyncRoot; } 1185 | } 1186 | 1187 | [Serializable] 1188 | public struct Enumerator : IEnumerator, System.Collections.IEnumerator 1189 | { 1190 | private UDictionary dictionary; 1191 | private int index; 1192 | private int version; 1193 | private TValue currentValue; 1194 | 1195 | internal Enumerator(UDictionary dictionary) 1196 | { 1197 | this.dictionary = dictionary; 1198 | version = dictionary.version; 1199 | index = 0; 1200 | currentValue = default(TValue); 1201 | } 1202 | 1203 | public void Dispose() 1204 | { 1205 | } 1206 | 1207 | public bool MoveNext() 1208 | { 1209 | if (version != dictionary.version) 1210 | { 1211 | throw new InvalidOperationException(); 1212 | } 1213 | 1214 | while ((uint)index < (uint)dictionary.count) 1215 | { 1216 | if (dictionary.entries[index].hashCode >= 0) 1217 | { 1218 | currentValue = dictionary.entries[index].value; 1219 | index++; 1220 | return true; 1221 | } 1222 | index++; 1223 | } 1224 | index = dictionary.count + 1; 1225 | currentValue = default(TValue); 1226 | return false; 1227 | } 1228 | 1229 | public TValue Current 1230 | { 1231 | get 1232 | { 1233 | return currentValue; 1234 | } 1235 | } 1236 | 1237 | System.Object System.Collections.IEnumerator.Current 1238 | { 1239 | get 1240 | { 1241 | if (index == 0 || (index == dictionary.count + 1)) 1242 | { 1243 | throw new InvalidOperationException(); 1244 | } 1245 | 1246 | return currentValue; 1247 | } 1248 | } 1249 | 1250 | void System.Collections.IEnumerator.Reset() 1251 | { 1252 | if (version != dictionary.version) 1253 | { 1254 | throw new InvalidOperationException(); 1255 | } 1256 | index = 0; 1257 | currentValue = default(TValue); 1258 | } 1259 | } 1260 | } 1261 | } 1262 | } 1263 | -------------------------------------------------------------------------------- /Runtime/UDictionary.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 97b50c3d00d772b4ba0f7c7d8ba8fb76 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Unity.SerializedDictionary.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Unity.SerializedDictionary", 3 | "references": [], 4 | "includePlatforms": [], 5 | "excludePlatforms": [], 6 | "allowUnsafeCode": false, 7 | "overrideReferences": false, 8 | "precompiledReferences": [], 9 | "autoReferenced": true, 10 | "defineConstraints": [], 11 | "versionDefines": [], 12 | "noEngineReferences": false 13 | } -------------------------------------------------------------------------------- /Runtime/Unity.SerializedDictionary.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c80069e02bbd5b9478436150910f0376 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /UnityDictionaryExamples.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prastiwar/UnitySerializedDictionary/0eb253b7eee1dc57b8b4e8a1882780fa37de741b/UnityDictionaryExamples.unitypackage -------------------------------------------------------------------------------- /UnityDictionaryExamples.unitypackage.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 794ba1c40874c124caa2faefd74341ac 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.prastiwar.unityserializeddictionary", 3 | "displayName": "UnitySerializedDictionary", 4 | "version": "1.0.7", 5 | "unity": "2019.4", 6 | "description": "Serialize Dictionary and see them visually in inspector", 7 | "keywords": ["Unity-Serialized Dictionary", "Serialized Dictionary", "Dictionary", "Serialized" ], 8 | "license": "MIT", 9 | "category": "Serialization", 10 | "dependencies": {} 11 | } 12 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f0e5a54b6a132d4479285f7f2a8f53d8 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------