Skip to content

Commit

Permalink
Augment SerializableDictionary to allow temporary duplicates in Editor
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
whebertML committed Dec 13, 2024
1 parent cf17c71 commit 274fee9
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 3 deletions.
59 changes: 57 additions & 2 deletions org.mixedrealitytoolkit.core/Utilities/SerializableDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,78 @@ public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IS

void ISerializationCallbackReceiver.OnBeforeSerialize()
{
#if !UNITY_EDITOR
entries.Clear();

foreach (KeyValuePair<TKey, TValue> 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<TKey, TValue> 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<TKey>.Default;

index = entries.FindIndex((entry) => keyComparer.Equals(entry.Key, key));
return index != -1;
}
#endif

[Serializable]
private struct SerializableDictionaryEntry
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the BSD 3-Clause

using MixedReality.Toolkit.Editor;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

Expand All @@ -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)
{
Expand Down Expand Up @@ -58,5 +70,41 @@ public override void OnInspectorGUI()

serializedObject.ApplyModifiedProperties();
}

private HashSet<string> GetDuplicateControllerMappings()
{
HashSet<string> duplicatedNames = new HashSet<string>();

SerializedProperty controllerMapping = serializedObject.FindProperty("controllerMapping");
SerializedProperty entries = controllerMapping?.FindPropertyRelative("entries");

if (entries != null && entries.arraySize > 0)
{
HashSet<int> seenInstanceIDs = new HashSet<int>();

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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public static InteractionModeManager Instance
public void InitializeControllers()
{
controllerMapping.Clear();
foreach (XRController xrController in FindObjectUtility.FindObjectsByType<XRController>())
foreach (XRBaseController xrController in FindObjectUtility.FindObjectsByType<XRBaseController>())
{
if (!controllerMapping.ContainsKey(xrController.gameObject))
{
Expand Down

0 comments on commit 274fee9

Please sign in to comment.