Skip to content

Commit 40894a6

Browse files
jonathoncobbCharlesWanMSRogPodgekeveleigh
authored
SpherePointer query optimization (#10324)
* SpherePointer query optimization The SpherePointer performs multiple queries per frame at times and with every collider returned it performs a costly GetComponent call on the collider to retrieve the NearInteractionGrabbable component. This PR optimizes that logic by adding a LRU cache to the SpherePointer to cache the retrieved NearInteractionGrabbable component. The cache holds a user defined number of entries, and the cache is purged when the SpherePointer is destroyed. Editor mode unit tests are implemented for the new utilities class LRUCache. * fixing lingering namespace issues * fixed bug where the currentGrabable wasn't cached * fixed grablayermasktest * Update Assets/MRTK/SDK/Features/Utilities/LRUCache.cs Co-authored-by: Kurtis <[email protected]> * Make LRUCache internal rather than public Co-authored-by: Charles Wan <[email protected]> Co-authored-by: Roger Liu <[email protected]> Co-authored-by: Kurtis <[email protected]>
1 parent 7fb4dba commit 40894a6

File tree

7 files changed

+648
-13
lines changed

7 files changed

+648
-13
lines changed

Assets/MRTK/SDK/Features/UX/Scripts/Pointers/SpherePointer.cs

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ public bool IgnoreCollidersNotInFOV
177177
set => ignoreCollidersNotInFOV = value;
178178
}
179179

180+
[SerializeField]
181+
private int nearInteractableCacheCapacity = 50;
182+
private LRUCache<int, NearInteractionGrabbable> nearInteractionGrabbableCache;
183+
180184
private SpherePointerQueryInfo queryBufferNearObjectRadius;
181185
private SpherePointerQueryInfo queryBufferInteractionRadius;
182186

@@ -201,6 +205,7 @@ private void Awake()
201205
{
202206
queryBufferNearObjectRadius = new SpherePointerQueryInfo(sceneQueryBufferSize, Mathf.Max(NearObjectRadius, SphereCastRadius), NearObjectSectorAngle, PullbackDistance, nearObjectSmoothingFactor, true);
203207
queryBufferInteractionRadius = new SpherePointerQueryInfo(sceneQueryBufferSize, SphereCastRadius, 360.0f, 0.0f, 0.0f);
208+
nearInteractionGrabbableCache = new LRUCache<int, NearInteractionGrabbable>(nearInteractableCacheCapacity);
204209
}
205210

206211
private static readonly ProfilerMarker OnPreSceneQueryPerfMarker = new ProfilerMarker("[MRTK] SpherePointer.OnPreSceneQuery");
@@ -233,7 +238,7 @@ public override void OnPreSceneQuery()
233238
{
234239
// First update queryBufferNearObjectRadius to see if there is a grabbable in the near interaction range
235240
queryBufferNearObjectRadius.TryUpdateQueryBufferForLayerMask(PrioritizedLayerMasksOverride[i], pointerPosition - pointerAxis * PullbackDistance, triggerInteraction);
236-
if (queryBufferNearObjectRadius.HasValidGrabbable(pointerPosition - pointerAxis * PullbackDistance, pointerAxis, ignoreCollidersNotInFOV))
241+
if (queryBufferNearObjectRadius.HasValidGrabbable(pointerPosition - pointerAxis * PullbackDistance, pointerAxis, ignoreCollidersNotInFOV, nearInteractionGrabbableCache))
237242
{
238243
break;
239244
}
@@ -246,9 +251,9 @@ public override void OnPreSceneQuery()
246251
{
247252
// Then update queryBufferInteractionRadius to see if there is a grabbable that can be interacted with
248253
queryBufferInteractionRadius.TryUpdateQueryBufferForLayerMask(PrioritizedLayerMasksOverride[i], pointerPosition, triggerInteraction);
249-
if (queryBufferInteractionRadius.HasValidGrabbable(pointerPosition, pointerAxis, ignoreCollidersNotInFOV))
254+
if (queryBufferInteractionRadius.HasValidGrabbable(pointerPosition, pointerAxis, ignoreCollidersNotInFOV, nearInteractionGrabbableCache))
250255
{
251-
hitObject = queryBufferInteractionRadius.GetClosestValidGrabbable(pointerPosition, pointerAxis, IgnoreCollidersNotInFOV, out hitPoint);
256+
hitObject = queryBufferInteractionRadius.GetClosestValidGrabbable(pointerPosition, pointerAxis, IgnoreCollidersNotInFOV, out hitPoint, nearInteractionGrabbableCache);
252257
if (hitObject != null)
253258
{
254259
hitDistance = (pointerPosition - hitPoint).magnitude;
@@ -462,15 +467,15 @@ public SpherePointerQueryInfo(int bufferSize, float radius, float angle, float m
462467
ignoreBoundsHandlesForQuery = ignoreBoundsHandles;
463468
}
464469

465-
public bool HasValidGrabbable(Vector3 pointerPosition, Vector3 pointerAxis, bool ignoreCollidersNotInFOV)
470+
public bool HasValidGrabbable(Vector3 pointerPosition, Vector3 pointerAxis, bool ignoreCollidersNotInFOV, LRUCache<int, NearInteractionGrabbable> componentCache)
466471
{
467472
Vector3 grabbablePosition = pointerPosition;
468473
NearInteractionGrabbable currentGrabbable = null;
469474

470475
for (int i = 0; i < numColliders; i++)
471476
{
472477
Collider collider = queryBuffer[i];
473-
if (IsColliderValidGrabbable(collider, ignoreCollidersNotInFOV, out currentGrabbable)
478+
if (IsColliderValidGrabbable(collider, ignoreCollidersNotInFOV, out currentGrabbable, componentCache)
474479
&& IsColliderPositionValid(collider, pointerPosition, pointerAxis, queryAngle, queryMinDistance, out grabbablePosition))
475480
{
476481
if (currentGrabbable != null)
@@ -491,7 +496,7 @@ public bool HasValidGrabbable(Vector3 pointerPosition, Vector3 pointerAxis, bool
491496

492497
/// <param name="pointerPosition">The position of the pointer to query against.</param>
493498
/// <param name="ignoreCollidersNotInFOV">Whether to ignore colliders that are not visible.</param>
494-
public GameObject GetClosestValidGrabbable(Vector3 pointerPosition, Vector3 pointerAxis, bool ignoreCollidersNotInFOV, out Vector3 hitPosition)
499+
public GameObject GetClosestValidGrabbable(Vector3 pointerPosition, Vector3 pointerAxis, bool ignoreCollidersNotInFOV, out Vector3 hitPosition, LRUCache<int, NearInteractionGrabbable> componentCache)
495500
{
496501
Vector3 colliderHitPoint = pointerPosition; ;
497502
NearInteractionGrabbable currentGrabbable = null;
@@ -504,7 +509,7 @@ public GameObject GetClosestValidGrabbable(Vector3 pointerPosition, Vector3 poin
504509
for (int i = 0; i < numColliders; i++)
505510
{
506511
Collider collider = queryBuffer[i];
507-
if (IsColliderValidGrabbable(collider, ignoreCollidersNotInFOV, out currentGrabbable)
512+
if (IsColliderValidGrabbable(collider, ignoreCollidersNotInFOV, out currentGrabbable, componentCache)
508513
&& IsColliderPositionValid(collider, pointerPosition, pointerAxis, queryAngle, queryMinDistance, out colliderHitPoint))
509514
{
510515
float currentDistance = (pointerPosition - colliderHitPoint).sqrMagnitude;
@@ -576,13 +581,24 @@ public void TryUpdateQueryBufferForLayerMask(LayerMask layerMask, Vector3 pointe
576581
}
577582
}
578583

579-
public bool IsColliderValidGrabbable(Collider collider, bool ignoreCollidersNotInFOV, out NearInteractionGrabbable currentGrabbable)
584+
public bool IsColliderValidGrabbable(Collider collider, bool ignoreCollidersNotInFOV, out NearInteractionGrabbable currentGrabbable, LRUCache<int, NearInteractionGrabbable> componentCache)
580585
{
581586
// Check if the collider has a grabbable component which is valid
582-
currentGrabbable = collider.GetComponent<NearInteractionGrabbable>();
587+
int instanceId = collider.gameObject.GetInstanceID();
588+
if (!componentCache.TryGetValue(instanceId, out currentGrabbable))
589+
{
590+
currentGrabbable = collider.gameObject.GetComponent<NearInteractionGrabbable>();
591+
if (currentGrabbable != null)
592+
{
593+
componentCache.Add(instanceId, currentGrabbable);
594+
}
595+
}
596+
583597
bool isValidGrabbable = (currentGrabbable != null) && !(ignoreBoundsHandlesForQuery && currentGrabbable.IsBoundsHandles);
584598
if (!isValidGrabbable)
585599
{
600+
// Remove it from the cache if the grabbable is no longer valid for the object
601+
componentCache.Remove(instanceId);
586602
return false;
587603
}
588604

0 commit comments

Comments
 (0)