Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Component Cache #11686

Merged
merged 10 commits into from
Jul 7, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ private void Awake()
previousSceneButton.enabled = IsSceneValid(SceneManager.GetActiveScene().buildIndex - 1);
nextSceneButton.enabled = IsSceneValid(SceneManager.GetActiveScene().buildIndex + 1);

SimpleProfiler profiler = Object.FindObjectOfType<SimpleProfiler>(true);
SimpleProfiler profiler = ComponentCache<SimpleProfiler>.FindFirstInstance(FindObjectsInactive.Include);
marlenaklein-msft marked this conversation as resolved.
Show resolved Hide resolved
if (profiler != null)
{
profilerObject = profiler.gameObject;
Expand Down
82 changes: 82 additions & 0 deletions com.microsoft.mrtk.core/Utilities/ComponentCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using UnityEngine;

namespace Microsoft.MixedReality.Toolkit
{

marlenaklein-msft marked this conversation as resolved.
Show resolved Hide resolved
/// <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;
private static T[] cacheList = null;

/// <summary>
/// Finds the first instance of an object of the given type.
/// </summary>
/// <param name="includeInactive">Specifies whether the search should include inactive objects.</param>
/// <returns>
/// The first instance of an object of the given type.
/// </returns>
public static T FindFirstInstance(FindObjectsInactive includeInactive = FindObjectsInactive.Exclude)
marlenaklein-msft marked this conversation as resolved.
Show resolved Hide resolved
{
_ = TryFindFirstInstance(out T result, includeInactive);
return result;
}

/// <summary>
/// Finds the first instance of an object of the given type.
/// </summary>
/// <param name="includeInactive">Specifies whether the search should include inactive objects.</param>
/// <param name="result">A reference of the given type, which will be set to the first instance of an object if one is found.</param>
/// <returns>
/// True if an instance was found, and false otherwise.
/// </returns>
public static bool TryFindFirstInstance(out T result, FindObjectsInactive includeInactive)
{
if (cacheFirstInstance == null)
{
cacheFirstInstance = Object.FindFirstObjectByType<T>(includeInactive);
}

result = cacheFirstInstance;
return result != null;
}

/// <summary>
/// Finds all instances of a given type.
/// </summary>
/// <param name="includeInactive">Specifies whether the search should include inactive objects. Defaults to excluding inactive objects.</param>
/// <returns>
/// All instances of a given type.
/// </returns>
public static T[] FindInstanceList(FindObjectsInactive includeInactive = FindObjectsInactive.Exclude)
marlenaklein-msft marked this conversation as resolved.
Show resolved Hide resolved
{
_ = TryFindInstanceList(out T[] result, includeInactive);
return result;
}

/// <summary>
/// Finds all instances of a given type.
/// </summary>
/// <param name="includeInactive">Specifies whether the search should include inactive objects.</param>
/// <param name="result">A reference to a list of the given type, which will be set to all instances that are found.</param>
/// <returns>
/// True if any instance was found, and false otherwise.
/// </returns>
public static bool TryFindInstanceList(out T[] result, FindObjectsInactive includeInactive)
{
if (cacheList == null)
{
cacheList = Object.FindObjectsByType<T>(includeInactive, FindObjectsSortMode.InstanceID);
}

result = cacheList;
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.

6 changes: 3 additions & 3 deletions com.microsoft.mrtk.input/Editor/InputValidation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ private static BuildValidationRule GenerateSpeechInteractorRule(BuildTargetGroup
return new BuildValidationRule()
{
IsRuleEnabled = () => (MRTKProjectValidation.GetLoadedSubsystemsForBuildTarget(buildTargetGroup)?.Any(s => typeof(KeywordRecognitionSubsystem).IsAssignableFrom(s.Type))).GetValueOrDefault()
marlenaklein-msft marked this conversation as resolved.
Show resolved Hide resolved
&& Object.FindObjectOfType<SpeechInteractor>(true),
&& ComponentCache<SpeechInteractor>.FindFirstInstance(FindObjectsInactive.Include),
Category = "MRTK3",
Message = "The speech interactor needs to be active and enabled in the scene to allow for speech interactions with interactables (e.g. buttons).",
CheckPredicate = () => Object.FindObjectOfType<SpeechInteractor>(true).isActiveAndEnabled,
FixIt = () => EditorGUIUtility.PingObject(Object.FindObjectOfType<SpeechInteractor>(true)),
CheckPredicate = () => ComponentCache<SpeechInteractor>.FindFirstInstance(FindObjectsInactive.Include).isActiveAndEnabled,
FixIt = () => EditorGUIUtility.PingObject(ComponentCache<SpeechInteractor>.FindFirstInstance(FindObjectsInactive.Include)),
FixItMessage = "Make sure the speech interactor component is enabled and in active in the hierarchy",
FixItAutomatic = false,
Error = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ internal class FlatScreenModeDetector : MonoBehaviour, IInteractionModeDetector

public void Awake()
{
ControllerLookup[] lookups = GameObject.FindObjectsOfType(typeof(ControllerLookup)) as ControllerLookup[];
ControllerLookup[] lookups = ComponentCache<ControllerLookup>.FindInstanceList();
marlenaklein-msft marked this conversation as resolved.
Show resolved Hide resolved
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.");
Expand Down
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 FindObjectsOfType<XRController>())
foreach (XRController xrController in ComponentCache<XRController>.FindInstanceList())
marlenaklein-msft marked this conversation as resolved.
Show resolved Hide resolved
{
if (!controllerMapping.ContainsKey(xrController.gameObject))
{
Expand Down Expand Up @@ -188,7 +188,7 @@ internal protected XRInteractionManager InteractionManager
{
if (interactionManager == null)
{
interactionManager = FindObjectOfType<XRInteractionManager>();
interactionManager = ComponentCache<XRInteractionManager>.FindFirstInstance();
}

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>.FindFirstInstance();
}

if (interactionManager == null)
Expand Down
5 changes: 1 addition & 4 deletions com.microsoft.mrtk.spatialmanipulation/Solvers/Solver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,12 +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[];
ControllerLookup[] lookups = ComponentCache<ControllerLookup>.FindInstanceList();
marlenaklein-msft marked this conversation as resolved.
Show resolved Hide resolved
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.");
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>.FindFirstInstance();
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>.FindFirstInstance();
}
}

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>.FindFirstInstance())
{
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