diff --git a/com.microsoft.mrtk.core/Utilities/ComponentCache.cs b/com.microsoft.mrtk.core/Utilities/ComponentCache.cs
new file mode 100644
index 00000000000..301a8202d96
--- /dev/null
+++ b/com.microsoft.mrtk.core/Utilities/ComponentCache.cs
@@ -0,0 +1,45 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using UnityEngine;
+
+namespace Microsoft.MixedReality.Toolkit
+{
+ ///
+ /// A static helper class which caches instances of a component of type T.
+ /// This is called in place of calling FindFirstObjectByType and FindObjectsByType to improve performance.
+ ///
+ public static class ComponentCache where T : Component
+ {
+ private static T cacheFirstInstance = null;
+
+ ///
+ /// Finds the first instance of an object of the given type.
+ ///
+ ///
+ /// The first instance of an object of the given type.
+ ///
+ public static T FindFirstActiveInstance()
+ {
+ _ = TryFindFirstActiveInstance(out T result);
+ return result;
+ }
+
+ ///
+ /// Finds the first instance of an object of the given type.
+ ///
+ ///
+ /// True if an instance was found, and false otherwise.
+ ///
+ public static bool TryFindFirstActiveInstance(out T result)
+ {
+ if (cacheFirstInstance == null || !cacheFirstInstance.gameObject.activeInHierarchy)
+ {
+ cacheFirstInstance = Object.FindFirstObjectByType();
+ }
+
+ result = cacheFirstInstance;
+ return result != null;
+ }
+ }
+}
diff --git a/com.microsoft.mrtk.core/Utilities/ComponentCache.cs.meta b/com.microsoft.mrtk.core/Utilities/ComponentCache.cs.meta
new file mode 100644
index 00000000000..ec09eb1f805
--- /dev/null
+++ b/com.microsoft.mrtk.core/Utilities/ComponentCache.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9f5a180a6a98b9c45bd7724dea467fa1
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.microsoft.mrtk.core/Utilities/ControllerLookup.cs b/com.microsoft.mrtk.core/Utilities/ControllerLookup.cs
index 2ef0c71509e..a84db2e7df9 100644
--- a/com.microsoft.mrtk.core/Utilities/ControllerLookup.cs
+++ b/com.microsoft.mrtk.core/Utilities/ControllerLookup.cs
@@ -56,5 +56,12 @@ public XRBaseController RightHandController
set => rightHandController = value;
}
+ private void OnValidate()
+ {
+ if (FindObjectsByType(FindObjectsSortMode.None).Length > 1)
+ {
+ Debug.LogWarning("Found more than one instance of the ControllerLookup class in the hierarchy. There should only be one");
+ }
+ }
}
}
diff --git a/com.microsoft.mrtk.input/InteractionModes/FlatScreenModeDetector.cs b/com.microsoft.mrtk.input/InteractionModes/FlatScreenModeDetector.cs
index f60f05b7345..67c6aab15f3 100644
--- a/com.microsoft.mrtk.input/InteractionModes/FlatScreenModeDetector.cs
+++ b/com.microsoft.mrtk.input/InteractionModes/FlatScreenModeDetector.cs
@@ -22,20 +22,7 @@ internal class FlatScreenModeDetector : MonoBehaviour, IInteractionModeDetector
public void Awake()
{
- ControllerLookup[] lookups = GameObject.FindObjectsOfType(typeof(ControllerLookup)) as ControllerLookup[];
- if (lookups.Length == 0)
- {
- Debug.LogError("Could not locate an instance of the ControllerLookup class in the hierarchy. It is recommended to add ControllerLookup to your camera rig.");
- }
- else if (lookups.Length > 1)
- {
- Debug.LogWarning("Found more than one instance of the ControllerLookup class in the hierarchy. Defaulting to the first instance.");
- controllerLookup = lookups[0];
- }
- else
- {
- controllerLookup = lookups[0];
- }
+ controllerLookup = ComponentCache.FindFirstActiveInstance();
}
///
diff --git a/com.microsoft.mrtk.input/InteractionModes/InteractionModeManager.cs b/com.microsoft.mrtk.input/InteractionModes/InteractionModeManager.cs
index d10071feac9..d0f904dc636 100644
--- a/com.microsoft.mrtk.input/InteractionModes/InteractionModeManager.cs
+++ b/com.microsoft.mrtk.input/InteractionModes/InteractionModeManager.cs
@@ -188,7 +188,7 @@ internal protected XRInteractionManager InteractionManager
{
if (interactionManager == null)
{
- interactionManager = FindObjectOfType();
+ interactionManager = ComponentCache.FindFirstActiveInstance();
}
return interactionManager;
diff --git a/com.microsoft.mrtk.input/InteractionModes/ProximityDetector.cs b/com.microsoft.mrtk.input/InteractionModes/ProximityDetector.cs
index 865cace1d8e..2d4ee2af379 100644
--- a/com.microsoft.mrtk.input/InteractionModes/ProximityDetector.cs
+++ b/com.microsoft.mrtk.input/InteractionModes/ProximityDetector.cs
@@ -63,7 +63,7 @@ private void Awake()
{
if (interactionManager == null)
{
- interactionManager = FindObjectOfType();
+ interactionManager = ComponentCache.FindFirstActiveInstance();
}
if (interactionManager == null)
diff --git a/com.microsoft.mrtk.spatialmanipulation/Solvers/Solver.cs b/com.microsoft.mrtk.spatialmanipulation/Solvers/Solver.cs
index b87918dd36c..96bcb7882b5 100644
--- a/com.microsoft.mrtk.spatialmanipulation/Solvers/Solver.cs
+++ b/com.microsoft.mrtk.spatialmanipulation/Solvers/Solver.cs
@@ -244,25 +244,9 @@ protected virtual void Start()
{
// Find the controller lookup class in the hierarchy. Solvers that require access to the
// left, right or gaze controllers will use the references stored in this class.
- //
- // While FindObjectsOfType can be an expensive method, the controllerLookup variable is
- // static. The hierarchy traversal will occur a maximum of once per scene.
if (controllerLookup == null)
{
- ControllerLookup[] lookups = GameObject.FindObjectsOfType(typeof(ControllerLookup)) as ControllerLookup[];
- if (lookups.Length == 0)
- {
- Debug.LogError("Could not locate an instance of the ControllerLookup class in the hierarchy. It is recommended to add ControllerLookup to your camera rig.");
- }
- else if (lookups.Length > 1)
- {
- Debug.LogWarning("Found more than one instance of the ControllerLookup class in the hierarchy. Defaulting to the first instance.");
- controllerLookup = lookups[0];
- }
- else
- {
- controllerLookup = lookups[0];
- }
+ controllerLookup = ComponentCache.FindFirstActiveInstance();
}
}
diff --git a/com.microsoft.mrtk.spatialmanipulation/Solvers/TapToPlace.cs b/com.microsoft.mrtk.spatialmanipulation/Solvers/TapToPlace.cs
index 7bd0351371f..ba961e78dd5 100644
--- a/com.microsoft.mrtk.spatialmanipulation/Solvers/TapToPlace.cs
+++ b/com.microsoft.mrtk.spatialmanipulation/Solvers/TapToPlace.cs
@@ -477,7 +477,7 @@ private void RegisterPlacementAction()
if (interactionManager == null)
{
- interactionManager = FindObjectOfType();
+ interactionManager = ComponentCache.FindFirstActiveInstance();
if (interactionManager == null)
{
Debug.LogError("No interaction manager found in scene. Please add an interaction manager to the scene.");
diff --git a/com.microsoft.mrtk.uxcore/Interop/UGUIInputAdapter.cs b/com.microsoft.mrtk.uxcore/Interop/UGUIInputAdapter.cs
index 95c73c74ba8..694626dbfc0 100644
--- a/com.microsoft.mrtk.uxcore/Interop/UGUIInputAdapter.cs
+++ b/com.microsoft.mrtk.uxcore/Interop/UGUIInputAdapter.cs
@@ -93,7 +93,7 @@ internal protected XRInteractionManager InteractionManager
else
{
// Otherwise, go find one.
- interactionManager = FindObjectOfType();
+ interactionManager = ComponentCache.FindFirstActiveInstance();
}
}
diff --git a/com.microsoft.mrtk.uxcore/SeeItSayIt/SeeItSayItLabelEnabler.cs b/com.microsoft.mrtk.uxcore/SeeItSayIt/SeeItSayItLabelEnabler.cs
index 4361e9bce8b..46405b9630d 100644
--- a/com.microsoft.mrtk.uxcore/SeeItSayIt/SeeItSayItLabelEnabler.cs
+++ b/com.microsoft.mrtk.uxcore/SeeItSayIt/SeeItSayItLabelEnabler.cs
@@ -3,6 +3,9 @@
using TMPro;
using UnityEngine;
+#if MRTK_INPUT_PRESENT && MRTK_SPEECH_PRESENT
+using Microsoft.MixedReality.Toolkit.Input;
+#endif
namespace Microsoft.MixedReality.Toolkit.UX
{
@@ -47,6 +50,12 @@ private void Start()
{
// Check if input and speech packages are present
#if MRTK_INPUT_PRESENT && MRTK_SPEECH_PRESENT
+ // If we can't find any active speech interactors, then do not enable the labels.
+ if (!ComponentCache.FindFirstActiveInstance())
+ {
+ return;
+ }
+
SeeItSayItLabel.SetActive(true);
// Children must be disabled so that they are not initially visible
diff --git a/com.microsoft.mrtk.uxcore/Tests/Runtime/SeeItSayItLabelEnablerTests.cs b/com.microsoft.mrtk.uxcore/Tests/Runtime/SeeItSayItLabelEnablerTests.cs
index 3c6b32296d8..e186eea74d7 100644
--- a/com.microsoft.mrtk.uxcore/Tests/Runtime/SeeItSayItLabelEnablerTests.cs
+++ b/com.microsoft.mrtk.uxcore/Tests/Runtime/SeeItSayItLabelEnablerTests.cs
@@ -2,8 +2,10 @@
// Licensed under the MIT License.
using System.Collections;
+using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.MixedReality.Toolkit.Core.Tests;
+using Microsoft.MixedReality.Toolkit.Input;
using Microsoft.MixedReality.Toolkit.Input.Tests;
using NUnit.Framework;
using TMPro;
@@ -21,11 +23,19 @@ public class SeeItSayItLabelEnablerTests : BaseRuntimeInputTests
[UnityTest]
public IEnumerator TestEnableAndSetLabel()
{
+#if MRTK_INPUT_PRESENT && MRTK_SPEECH_PRESENT
+ SpeechInteractor interactor = Object.FindAnyObjectByType(FindObjectsInactive.Include);
+ interactor.gameObject.SetActive(true);
+
GameObject testButton = SetUpButton(true, Control.None);
yield return null;
+ if (Application.isBatchMode)
+ {
+ LogAssert.Expect(LogType.Exception, new Regex("Speech recognition is not supported on this machine"));
+ }
Transform label = testButton.transform.GetChild(0);
-#if MRTK_INPUT_PRESENT && MRTK_SPEECH_PRESENT
+
Transform sublabel = label.transform.GetChild(0);
Assert.IsTrue(label.gameObject.activeSelf, "Label is enabled");
Assert.IsTrue(!sublabel.gameObject.activeSelf, "Child objects are disabled");
@@ -57,11 +67,18 @@ public IEnumerator TestVoiceCommandsUnavailable()
[UnityTest]
public IEnumerator TestPositionCanvasLabel()
{
+#if MRTK_INPUT_PRESENT && MRTK_SPEECH_PRESENT
+ SpeechInteractor interactor = Object.FindAnyObjectByType(FindObjectsInactive.Include);
+ interactor.gameObject.SetActive(true);
+
GameObject testButton = SetUpButton(true, Control.Canvas);
yield return null;
+ if (Application.isBatchMode)
+ {
+ LogAssert.Expect(LogType.Exception, new Regex("Speech recognition is not supported on this machine"));
+ }
Transform label = testButton.transform.GetChild(0);
-#if MRTK_INPUT_PRESENT && MRTK_SPEECH_PRESENT
RectTransform sublabel = label.transform.GetChild(0) as RectTransform;
Assert.AreEqual(sublabel.anchoredPosition3D, new Vector3(10, -30, -10), "Label is positioned correctly");
#else
@@ -76,11 +93,18 @@ public IEnumerator TestPositionCanvasLabel()
[UnityTest]
public IEnumerator TestPositionNonCanvasLabel()
{
+#if MRTK_INPUT_PRESENT && MRTK_SPEECH_PRESENT
+ SpeechInteractor interactor = Object.FindAnyObjectByType(FindObjectsInactive.Include);
+ interactor.gameObject.SetActive(true);
+
GameObject testButton = SetUpButton(true, Control.NonCanvas);
yield return null;
+ if (Application.isBatchMode)
+ {
+ LogAssert.Expect(LogType.Exception, new Regex("Speech recognition is not supported on this machine"));
+ }
Transform label = testButton.transform.GetChild(0);
-#if MRTK_INPUT_PRESENT && MRTK_SPEECH_PRESENT
Assert.AreEqual(label.transform.localPosition, new Vector3(10f, -.504f, -.004f), "Label is positioned correctly");
#else
Assert.IsTrue(!label.gameObject.activeSelf, "Did not enable label because voice commands unavailable.");