From a4a9aacc8636e21ef7c6b446f8c7042ba2ce736c Mon Sep 17 00:00:00 2001 From: Wyck Hebert Date: Tue, 10 Dec 2024 17:15:05 -0600 Subject: [PATCH] Augment SerializableDictionary to allow temporary duplicates in Editor * Also fixes an issue with the "Init Controllers" type lookup within InteractionModeManager.css to find XRBaseControllers. --- .../Utilities/SerializableDictionary.cs | 59 ++++++++++++++++++- .../InteractionModeManagerEditor.cs | 48 +++++++++++++++ .../InteractionModeManager.cs | 2 +- 3 files changed, 106 insertions(+), 3 deletions(-) diff --git a/org.mixedrealitytoolkit.core/Utilities/SerializableDictionary.cs b/org.mixedrealitytoolkit.core/Utilities/SerializableDictionary.cs index 88cd04ced..6b8317397 100644 --- a/org.mixedrealitytoolkit.core/Utilities/SerializableDictionary.cs +++ b/org.mixedrealitytoolkit.core/Utilities/SerializableDictionary.cs @@ -21,23 +21,78 @@ public class SerializableDictionary : Dictionary, IS void ISerializationCallbackReceiver.OnBeforeSerialize() { +#if !UNITY_EDITOR entries.Clear(); foreach (KeyValuePair pair in this) { entries.Add(new SerializableDictionaryEntry(pair.Key, pair.Value)); } +#else + // While in Editor, the serialized entries list is managed differently and is not necessarily a 1:1 representation of + // the dictionary. This allows for temporary duplicate keys, something the dictionary cannot do, while modifications + // are being made in the Inspector since the default behavior is to duplicate the last entry when adding a new one. + + // Override the first entry that has a matching key from the dictionary, otherwise add to entries. + foreach (KeyValuePair pair in this) + { + if (TryFindSerializableIndex(pair.Key, out int index)) + { + entries[index] = new SerializableDictionaryEntry(pair.Key, pair.Value); + } + else + { + entries.Add(new SerializableDictionaryEntry(pair.Key, pair.Value)); + } + } +#endif } void ISerializationCallbackReceiver.OnAfterDeserialize() { - this.Clear(); + base.Clear(); foreach (SerializableDictionaryEntry entry in entries) { - this.Add(entry.Key, entry.Value); + base.TryAdd(entry.Key, entry.Value); + } + } + +#if UNITY_EDITOR + public new void Clear() + { + entries.Clear(); + base.Clear(); + } + + public new bool Remove(TKey key, out TValue value) + { + if (base.Remove(key, out value)) + { + if (TryFindSerializableIndex(key, out int index)) + { + entries.RemoveAt(index); + } + + return true; } + + return false; + } + + public new bool Remove(TKey key) + { + return Remove(key, out _); + } + + private bool TryFindSerializableIndex(TKey key, out int index) + { + var keyComparer = EqualityComparer.Default; + + index = entries.FindIndex((entry) => keyComparer.Equals(entry.Key, key)); + return index != -1; } +#endif [Serializable] private struct SerializableDictionaryEntry diff --git a/org.mixedrealitytoolkit.input/Editor/Inspectors/InteractionModeManagerEditor.cs b/org.mixedrealitytoolkit.input/Editor/Inspectors/InteractionModeManagerEditor.cs index c5828612d..a89737f80 100644 --- a/org.mixedrealitytoolkit.input/Editor/Inspectors/InteractionModeManagerEditor.cs +++ b/org.mixedrealitytoolkit.input/Editor/Inspectors/InteractionModeManagerEditor.cs @@ -2,6 +2,7 @@ // Licensed under the BSD 3-Clause using MixedReality.Toolkit.Editor; +using System.Collections.Generic; using UnityEditor; using UnityEngine; @@ -27,6 +28,17 @@ public override void OnInspectorGUI() InteractionModeManager interactionModeManager = (InteractionModeManager)target; // Raise lots of errors if the interaction mode manager is configured incorrectly + var duplicateControllerMappings = GetDuplicateControllerMappings(); + if (duplicateControllerMappings.Count > 0) + { + var duplicatedNameString = interactionModeManager.CompileDuplicatedNames(duplicateControllerMappings); + + InspectorUIUtility.DrawError($"Duplicate controller mapping keys detected in the interaction mode manager on {interactionModeManager.gameObject.name}. " + + $"Please check the following controller mappings: {duplicatedNameString}"); + + GUI.color = InspectorUIUtility.ErrorColor; + } + var duplicatedNames = interactionModeManager.GetDuplicateInteractionModes(); if (duplicatedNames.Count > 0) { @@ -58,5 +70,41 @@ public override void OnInspectorGUI() serializedObject.ApplyModifiedProperties(); } + + private HashSet GetDuplicateControllerMappings() + { + HashSet duplicatedNames = new HashSet(); + + SerializedProperty controllerMapping = serializedObject.FindProperty("controllerMapping"); + SerializedProperty entries = controllerMapping?.FindPropertyRelative("entries"); + + if (entries != null && entries.arraySize > 0) + { + HashSet seenInstanceIDs = new HashSet(); + + for (int i = 0; i < entries.arraySize; ++i) + { + SerializedProperty entry = entries.GetArrayElementAtIndex(i); + SerializedProperty key = entry.FindPropertyRelative("key"); + + int instanceID = key != null && key.objectReferenceValue != null ? + key.objectReferenceValue.GetInstanceID() : 0; + + if (seenInstanceIDs.Contains(instanceID)) + { + string duplicateName = key != null && key.objectReferenceValue != null ? + key.objectReferenceValue.name : "None (Game Object)"; + + duplicatedNames.Add(duplicateName); + } + else + { + seenInstanceIDs.Add(instanceID); + } + } + } + + return duplicatedNames; + } } } diff --git a/org.mixedrealitytoolkit.input/InteractionModes/InteractionModeManager.cs b/org.mixedrealitytoolkit.input/InteractionModes/InteractionModeManager.cs index 58df834bf..b80b0723d 100644 --- a/org.mixedrealitytoolkit.input/InteractionModes/InteractionModeManager.cs +++ b/org.mixedrealitytoolkit.input/InteractionModes/InteractionModeManager.cs @@ -71,7 +71,7 @@ public static InteractionModeManager Instance public void InitializeControllers() { controllerMapping.Clear(); - foreach (XRController xrController in FindObjectUtility.FindObjectsByType()) + foreach (XRBaseController xrController in FindObjectUtility.FindObjectsByType()) { if (!controllerMapping.ContainsKey(xrController.gameObject)) {