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.");