Skip to content

Commit

Permalink
Add Component Cache (#11686)
Browse files Browse the repository at this point in the history
Implements a generic component cache.
  • Loading branch information
marlenaklein-msft authored Jul 7, 2023
1 parent 8648ef3 commit 7bff152
Show file tree
Hide file tree
Showing 11 changed files with 105 additions and 38 deletions.
45 changes: 45 additions & 0 deletions com.microsoft.mrtk.core/Utilities/ComponentCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using UnityEngine;

namespace Microsoft.MixedReality.Toolkit
{
/// <summary>
/// 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.
/// </summary>
public static class ComponentCache<T> where T : Component
{
private static T cacheFirstInstance = null;

/// <summary>
/// Finds the first instance of an object of the given type.
/// </summary>
/// <returns>
/// The first instance of an object of the given type.
/// </returns>
public static T FindFirstActiveInstance()
{
_ = TryFindFirstActiveInstance(out T result);
return result;
}

/// <summary>
/// Finds the first instance of an object of the given type.
/// </summary>
/// <returns>
/// True if an instance was found, and false otherwise.
/// </returns>
public static bool TryFindFirstActiveInstance(out T result)
{
if (cacheFirstInstance == null || !cacheFirstInstance.gameObject.activeInHierarchy)
{
cacheFirstInstance = Object.FindFirstObjectByType<T>();
}

result = cacheFirstInstance;
return result != null;
}
}
}
11 changes: 11 additions & 0 deletions com.microsoft.mrtk.core/Utilities/ComponentCache.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions com.microsoft.mrtk.core/Utilities/ControllerLookup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,12 @@ public XRBaseController RightHandController
set => rightHandController = value;
}

private void OnValidate()
{
if (FindObjectsByType<ControllerLookup>(FindObjectsSortMode.None).Length > 1)
{
Debug.LogWarning("Found more than one instance of the ControllerLookup class in the hierarchy. There should only be one");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<ControllerLookup>.FindFirstActiveInstance();
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ internal protected XRInteractionManager InteractionManager
{
if (interactionManager == null)
{
interactionManager = FindObjectOfType<XRInteractionManager>();
interactionManager = ComponentCache<XRInteractionManager>.FindFirstActiveInstance();
}

return interactionManager;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ private void Awake()
{
if (interactionManager == null)
{
interactionManager = FindObjectOfType<XRInteractionManager>();
interactionManager = ComponentCache<XRInteractionManager>.FindFirstActiveInstance();
}

if (interactionManager == null)
Expand Down
18 changes: 1 addition & 17 deletions com.microsoft.mrtk.spatialmanipulation/Solvers/Solver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ControllerLookup>.FindFirstActiveInstance();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ private void RegisterPlacementAction()

if (interactionManager == null)
{
interactionManager = FindObjectOfType<XRInteractionManager>();
interactionManager = ComponentCache<XRInteractionManager>.FindFirstActiveInstance();
if (interactionManager == null)
{
Debug.LogError("No interaction manager found in scene. Please add an interaction manager to the scene.");
Expand Down
2 changes: 1 addition & 1 deletion com.microsoft.mrtk.uxcore/Interop/UGUIInputAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ internal protected XRInteractionManager InteractionManager
else
{
// Otherwise, go find one.
interactionManager = FindObjectOfType<XRInteractionManager>();
interactionManager = ComponentCache<XRInteractionManager>.FindFirstActiveInstance();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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<SpeechInteractor>.FindFirstActiveInstance())
{
return;
}

SeeItSayItLabel.SetActive(true);

// Children must be disabled so that they are not initially visible
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,11 +23,19 @@ public class SeeItSayItLabelEnablerTests : BaseRuntimeInputTests
[UnityTest]
public IEnumerator TestEnableAndSetLabel()
{
#if MRTK_INPUT_PRESENT && MRTK_SPEECH_PRESENT
SpeechInteractor interactor = Object.FindAnyObjectByType<SpeechInteractor>(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");
Expand Down Expand Up @@ -57,11 +67,18 @@ public IEnumerator TestVoiceCommandsUnavailable()
[UnityTest]
public IEnumerator TestPositionCanvasLabel()
{
#if MRTK_INPUT_PRESENT && MRTK_SPEECH_PRESENT
SpeechInteractor interactor = Object.FindAnyObjectByType<SpeechInteractor>(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
Expand All @@ -76,11 +93,18 @@ public IEnumerator TestPositionCanvasLabel()
[UnityTest]
public IEnumerator TestPositionNonCanvasLabel()
{
#if MRTK_INPUT_PRESENT && MRTK_SPEECH_PRESENT
SpeechInteractor interactor = Object.FindAnyObjectByType<SpeechInteractor>(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.");
Expand Down

0 comments on commit 7bff152

Please sign in to comment.