From 274fee90a10c9a79b179c02cb75732b2ffbdaa3c 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.InitializeControllers() to find XRBaseControllers instead of XRControllers, since the legacy MRTK controllers derive from XRBaseControllers, and the GUI button previously wasn't finding any controller to initialize in the Inspector. --- .../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)) {