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.css to find XRBaseControllers.
  • Loading branch information
whebertML committed Dec 11, 2024
1 parent cf17c71 commit a4a9aac
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 a4a9aac

Please sign in to comment.