// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
// Copyright (c) (2024) Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Software License Agreement, located here: https://www.magicleap.com/software-license-agreement-ml2
// Terms and conditions applicable to third-party materials accompanying this distribution may also be found in the top-level NOTICE file appearing herein.
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
using System;
using System.Collections.Generic;
using System.Linq;
using MagicLeap.OpenXR.Constants;
using MagicLeap.OpenXR.Futures;
using MagicLeap.OpenXR.Spaces;
using MagicLeap.OpenXR.Subsystems;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
using UnityEngine.LowLevel;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
using UnityEngine.XR.Management;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.NativeTypes;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.XR.OpenXR.Features;
#endif
namespace MagicLeap.OpenXR.Features.SpatialAnchors
{
#if UNITY_EDITOR
[OpenXRFeature(UiName = "Magic Leap 2 Spatial Anchors Storage",
Desc = "Expand Spatial Anchors to allow to Publish, Delete, and update on Localized Maps.",
Company = "Magic Leap",
Version = "1.0.0",
BuildTargetGroups = new[] { BuildTargetGroup.Android, BuildTargetGroup.Standalone },
FeatureId = FeatureId,
OpenxrExtensionStrings = ExtensionName
)]
#endif // UNITY_EDITOR
public class MagicLeapSpatialAnchorsStorageFeature : MagicLeapOpenXRFeatureBase
{
public const string FeatureId = "com.magicleap.openxr.feature.ml2_spatialanchorstorage";
private const string ExtensionName = "XR_ML_spatial_anchors_storage XR_EXT_future XR_KHR_locate_spaces";
///
/// Receive an ulong AnchorId and a string anchorMapPositionId for the published anchor.
///
public event Action OnPublishComplete;
///
/// Receive a list of string anchorMapPositionIds of anchors returned from the Query of the Localized Map.
///
public event Action> OnQueryComplete;
///
/// Receive a list of string anchorMapPositionIds of anchors that have been deleted from the Localized Map.
///
public event Action> OnDeletedComplete;
///
/// Receive a list of string anchorMapPositionIds of anchors that have had their expiration updated on the Localized Map.
///
public event Action> OnUpdateExpirationCompleted;
///
/// Receive the created anchor's Pose, the anchor's ulong AnchorId, the anchor's string anchorMapPositionId for the localized Map, and the XResult of the request to create the anchor.
///
public event Action OnCreationCompleteFromStorage;
private MagicLeapSpatialAnchorsFeature anchorsFeature;
private bool startedSpatialAnchorStorage;
private MagicLeapSpatialAnchorsStorageNativeFunctions spatialAnchorsStorageNativeFunctions;
private SpacesNativeFunctions locateSpacesNativeFunctions;
private MLXrAnchorSubsystem activeSubsystem;
private ulong storageHandle;
private struct AnchorsStorageData
{
internal int Count;
internal List Uuid;
}
private struct AnchorsStoragePendingPublish
{
internal int Count;
internal List AnchorIds;
}
private readonly HashSet pendingStorageAnchors = new HashSet();
private readonly Dictionary pendingStorageAnchorsData = new Dictionary();
private readonly HashSet pendingPublishRequests = new HashSet();
private readonly Dictionary pendingStorageAnchorsPublishData = new Dictionary();
private readonly HashSet pendingQueries = new HashSet();
private readonly HashSet pendingDeleteRequests = new HashSet();
private readonly Dictionary pendingStorageAnchorsDeleteData = new Dictionary();
private readonly HashSet pendingUpdateRequests = new HashSet();
private readonly Dictionary pendingStorageAnchorsUpdateData = new Dictionary();
private struct AnchorsStorageUpdateType
{ }
protected override bool OnInstanceCreate(ulong xrInstance)
{
var exts = ExtensionName.Split(' ');
foreach (var ext in exts)
{
if (!OpenXRRuntime.IsExtensionEnabled(ext))
{
Debug.LogError($"{ext} is not enabled. Disabling {nameof(MagicLeapSpatialAnchorsStorageFeature)}");
return false;
}
}
var updateSystem = new PlayerLoopSystem
{
subSystemList = Array.Empty(),
type = typeof(AnchorsStorageUpdateType),
updateDelegate = AnchorsStoragePlayerLoop,
};
var playerLoop = PlayerLoop.GetCurrentPlayerLoop();
if (!PlayerLoopUtil.InstallIntoPlayerLoop(ref playerLoop, updateSystem, PlayerLoopUtil.InstallPath))
throw new Exception("Unable to install Spatial Anchors Storage Update delegate into player loop!");
PlayerLoop.SetPlayerLoop(playerLoop);
bool instanceCreateResult = base.OnInstanceCreate(xrInstance);
spatialAnchorsStorageNativeFunctions = CreateNativeFunctions();
locateSpacesNativeFunctions = CreateNativeFunctions();
return instanceCreateResult;
}
public bool CreateSpatialAnchorsFromStorage(List anchorMapPositionIds)
{
if (!startedSpatialAnchorStorage)
{
return false;
}
if (anchorMapPositionIds.Count == 0)
{
Debug.LogError("CreateSpatialAnchors sent an empty list of AnchorMapPositionIds.");
return false;
}
if(anchorsFeature == null)
{
anchorsFeature = OpenXRSettings.Instance.GetFeature();
}
if (!anchorsFeature.enabled)
{
Debug.LogError("CreateSpatialAnchors requires an active MagicLeapSpatialAnchorsFeature OpenXRFeature.");
return false;
}
unsafe
{
NativeArray anchorUuidsToCreate = new NativeArray(anchorMapPositionIds.Count, Allocator.Temp);
for (int i = 0; i < anchorUuidsToCreate.Length; ++i)
{
anchorUuidsToCreate[i] = new XrUUID(anchorMapPositionIds[i]);
}
var createInfo = new XrSpatialAnchorsCreateInfoFromUuids
{
Type = XrSpatialAnchorsStorageStructTypes.XrTypeSpatialAnchorsCreateInfoFromUUIDs,
Storage = storageHandle,
UuidCount = (uint)anchorMapPositionIds.Count,
Uuids = (XrUUID*)anchorUuidsToCreate.GetUnsafePtr()
};
var baseHeader = (XrSpatialAnchorsCreateInfoBaseHeader*)(&createInfo);
XrResult resultCode = anchorsFeature.SpatialAnchorsNativeFunctions.XrCreateSpatialAnchorsAsync(AppSession, baseHeader, out ulong pendingFuture);
bool result = Utils.DidXrCallSucceed(resultCode, nameof(anchorsFeature.SpatialAnchorsNativeFunctions.XrCreateSpatialAnchorsAsync));
if(!result)
{
Debug.LogError("CreateSpatialAnchors failed to send request to create anchors from AnchorMapPositionId list.");
}
else
{
pendingStorageAnchors.Add(pendingFuture);
var savedPendingData = new AnchorsStorageData
{
Count = anchorMapPositionIds.Count,
Uuid = anchorMapPositionIds
};
pendingStorageAnchorsData.Add(pendingFuture, savedPendingData);
}
return result;
}
}
///
/// Determine the maximum number of anchors to accept their completion status each update.
///
/// The position of the center of the query
/// The radius of the search area in meters.
public bool QueryStoredSpatialAnchors(Vector3 position, float radius)
{
if (!startedSpatialAnchorStorage)
{
return false;
}
unsafe
{
var queryInfo = new XrSpatialAnchorsQueryInfoRadius
{
Type = XrSpatialAnchorsStorageStructTypes.XrTypeSpatialAnchorsQueryInfoRadius,
Center = position.InvertZ(),
Radius = radius,
Time = NextPredictedDisplayTime,
BaseSpace = AppSpace
};
var baseHeader = (XrSpatialAnchorsQueryInfoBaseHeader*)(&queryInfo);
XrResult resultCode = spatialAnchorsStorageNativeFunctions.XrQuerySpatialAnchorsAsync(storageHandle, baseHeader, out ulong pendingFuture);
bool result = Utils.DidXrCallSucceed(resultCode, nameof(spatialAnchorsStorageNativeFunctions.XrQuerySpatialAnchorsAsync));
if (!result)
{
Debug.LogError("CreateSpatialAnchors failed to send request to create anchors from AnchorMapPositionId list.");
}
else
{
pendingQueries.Add(pendingFuture);
}
return result;
}
}
///
/// Publish local anchors to Spatial Anchor Storage using the MagicLeap Anchor Id.
///
/// The list of AnchorIds to publish. These were assigned in the OnCreationComplete event in MagicLeapSpatialAnchorsFeature.
/// The time in seconds since epoch after which these anchors may: expire. Use 0 for permanent anchors. The system may retain them longer.
public bool PublishSpatialAnchorsToStorage(List anchorIds, ulong expiration)
{
if (!startedSpatialAnchorStorage)
{
return false;
}
if (anchorIds.Count == 0)
{
Debug.LogError("PublishSpatialAnchorsToStorage sent an empty list of AnchorIds.");
return false;
}
unsafe
{
using NativeArray anchorsToPublish = new NativeArray(anchorIds.ToArray(), Allocator.Temp);
var publishInfo = new XrSpatialAnchorsPublishInfo
{
Type = XrSpatialAnchorsStorageStructTypes.XrTypeSpatialAnchorsPublishInfo,
AnchorCount = (uint)anchorIds.Count,
Anchors = (ulong*)anchorsToPublish.GetUnsafePtr(),
Expiration = expiration
};
XrResult resultCode = spatialAnchorsStorageNativeFunctions.XrPublishSpatialAnchorsAsync(storageHandle, &publishInfo, out ulong pendingFuture);
bool result = Utils.DidXrCallSucceed(resultCode, nameof(anchorsFeature.SpatialAnchorsNativeFunctions.XrCreateSpatialAnchorsAsync));
if (!result)
{
Debug.LogError("PublishSpatialAnchorsToStorage failed to send request to publish anchors from AnchorId list.");
return false;
}
pendingPublishRequests.Add(pendingFuture);
var savedPendingData = new AnchorsStoragePendingPublish
{
Count = anchorIds.Count,
AnchorIds = anchorIds
};
pendingStorageAnchorsPublishData.Add(pendingFuture, savedPendingData);
return true;
}
}
///
/// Publish local anchors to Spatial Anchor Storage using ARAnchors. Will return false if XRAnchorSubsystem is not loaded.
///
/// The list of ARAnchors to publish. TrackingState must be Tracking to be valid for publish. Will be ignored if not.
/// The time in seconds since epoch after which these anchors may: expire. Use 0 for permanent anchors. The system may retain them longer.
public bool PublishSpatialAnchorsToStorage(List anchors, ulong expiration)
{
if (!startedSpatialAnchorStorage)
{
return false;
}
if(activeSubsystem == null)
{
return false;
}
List anchorIds = new List();
foreach (ARAnchor anchor in anchors)
{
if(anchor.trackingState != TrackingState.Tracking)
{
continue;
}
ulong anchorid = activeSubsystem.GetAnchorId(anchor);
if (anchorid != 0u)
{
anchorIds.Add(anchorid);
}
else
{
Debug.LogWarning($"PublishSpatialAnchorsToStorage is unable to locate an ID for {anchor.trackableId} and it will be excluded from the list to publish.");
}
}
return PublishSpatialAnchorsToStorage(anchorIds, expiration);
}
[Obsolete("DeleteStoredSpatialAnchor is obsolete. Use DeleteStoredSpatialAnchors instead.", false)]
public bool DeleteStoredSpatialAnchor(List anchorMapPositionIds)
{
return DeleteStoredSpatialAnchors(anchorMapPositionIds);
}
///
/// Delete published anchors from Spatial Anchor Storage.
///
/// The list of AnchorMapPositionIds to Delete. These were assigned in the OnPublishComplete event in MagicLeapSpatialAnchorsStorageFeature.
public bool DeleteStoredSpatialAnchors(List anchorMapPositionIds)
{
if (!startedSpatialAnchorStorage)
{
return false;
}
if (anchorMapPositionIds.Count == 0)
{
Debug.LogError("DeleteStoredSpatialAnchor sent an empty list of AnchorMapPositionIds.");
return false;
}
unsafe
{
NativeArray anchorUuidsToDelete = new NativeArray(anchorMapPositionIds.Count, Allocator.Temp);
for (int i = 0; i < anchorUuidsToDelete.Length; ++i)
{
anchorUuidsToDelete[i] = new XrUUID(anchorMapPositionIds[i]);
}
var deleteInfo = new XrSpatialAnchorsDeleteInfo
{
Type = XrSpatialAnchorsStorageStructTypes.XrTypeSpatialAnchorsDeleteInfo,
UuidCount = (uint)anchorMapPositionIds.Count,
Uuids = (XrUUID*)anchorUuidsToDelete.GetUnsafePtr()
};
XrResult resultCode = spatialAnchorsStorageNativeFunctions.XrDeleteSpatialAnchorsAsync(storageHandle, &deleteInfo, out ulong pendingFuture);
bool result = Utils.DidXrCallSucceed(resultCode, nameof(spatialAnchorsStorageNativeFunctions.XrDeleteSpatialAnchorsAsync));
if(!result)
{
Debug.LogError("CreateSpatialAnchors failed to send request to create anchors from AnchorMapPositionId list.");
return false;
}
pendingDeleteRequests.Add(pendingFuture);
var savedDeleteData = new AnchorsStorageData
{
Count = anchorMapPositionIds.Count,
Uuid = anchorMapPositionIds
};
pendingStorageAnchorsDeleteData.Add(pendingFuture, savedDeleteData);
return true;
}
}
public bool DeleteStoredSpatialAnchors(List anchors)
{
if (!startedSpatialAnchorStorage)
{
return false;
}
if (activeSubsystem == null)
{
return false;
}
List anchorMapPositionIds = new List();
foreach (ARAnchor anchor in anchors)
{
if (anchor.trackingState != TrackingState.Tracking)
{
continue;
}
string anchorMapPositionId = activeSubsystem.GetAnchorMapPositionId(anchor);
if (!String.IsNullOrEmpty(anchorMapPositionId))
{
anchorMapPositionIds.Add(anchorMapPositionId);
}
else
{
Debug.LogWarning($"DeleteStoredSpatialAnchor is unable to locate an AnchorMapPositionId for {anchor.trackableId} and it will be excluded from the list of anchors to be deleted.");
}
}
return DeleteStoredSpatialAnchors(anchorMapPositionIds);
}
///
/// Update the expiration time for published anchors in Spatial Anchor Storage.
///
/// The list of AnchorMapPositionIds to Delete. These were assigned in the OnPublishComplete event in MagicLeapSpatialAnchorsStorageFeature.
/// The time in seconds since epoch after which these anchors may: expire. The system may retain them longer.
[Obsolete("UpdateExpirationonStoredSpatialAnchor will be deprecated. Use UpdateExpirationForStoredSpatialAnchor.")]
public bool UpdateExpirationonStoredSpatialAnchor(List anchorMapPositionIds, ulong expiration) => UpdateExpirationForStoredSpatialAnchor(anchorMapPositionIds, expiration);
///
/// Update the expiration time for published anchors in Spatial Anchor Storage.
///
/// The list of AnchorMapPositionIds to Delete. These were assigned in the OnPublishComplete event in MagicLeapSpatialAnchorsStorageFeature.
/// The time in seconds since epoch after which these anchors may: expire. The system may retain them longer.
public bool UpdateExpirationForStoredSpatialAnchor(List anchorMapPositionIds, ulong expiration)
{
if (!startedSpatialAnchorStorage)
{
return false;
}
if (anchorMapPositionIds.Count == 0)
{
Debug.LogError($"{nameof(UpdateExpirationForStoredSpatialAnchor)} sent an empty list of AnchorMapPositionIds.");
return false;
}
unsafe
{
NativeArray anchorUuidsToUpdate = new NativeArray(anchorMapPositionIds.Count, Allocator.Temp);
for (int i = 0; i < anchorUuidsToUpdate.Length; ++i)
{
anchorUuidsToUpdate[i] = new XrUUID(anchorMapPositionIds[i]);
}
var updateInfo = new XrSpatialAnchorsUpdateExpirationInfo
{
Type = XrSpatialAnchorsStorageStructTypes.XrTypeSpatialAnchorsUpdateExpirationInfo,
UuidCount = (uint)anchorMapPositionIds.Count,
Uuids = (XrUUID*)anchorUuidsToUpdate.GetUnsafePtr(),
Expiration = expiration
};
XrResult resultCode = spatialAnchorsStorageNativeFunctions.XrUpdateSpatialAnchorsExpirationAsync(storageHandle, &updateInfo, out ulong pendingFuture);
bool result = Utils.DidXrCallSucceed(resultCode, nameof(spatialAnchorsStorageNativeFunctions.XrUpdateSpatialAnchorsExpirationAsync));
if (!result)
{
Debug.LogError($"{nameof(UpdateExpirationForStoredSpatialAnchor)} failed to send request to update anchors expiration from AnchorMapPositionId list.");
return false;
}
pendingUpdateRequests.Add(pendingFuture);
var savedUpdateData = new AnchorsStorageData
{
Count = anchorMapPositionIds.Count,
Uuid = anchorMapPositionIds
};
pendingStorageAnchorsUpdateData.Add(pendingFuture, savedUpdateData);
return true;
}
}
public bool UpdateExpirationForStoredSpatialAnchor(List anchors, ulong expiration)
{
if (!startedSpatialAnchorStorage)
{
return false;
}
if (activeSubsystem == null)
{
return false;
}
List anchorMapPositionIds = new List();
foreach (ARAnchor anchor in anchors)
{
if (anchor.trackingState != TrackingState.Tracking)
{
continue;
}
string anchorMapPositionId = activeSubsystem.GetAnchorMapPositionId(anchor);
if (!String.IsNullOrEmpty(anchorMapPositionId))
{
anchorMapPositionIds.Add(anchorMapPositionId);
}
else
{
Debug.LogWarning($"UpdateExpirationForStoredSpatialAnchor is unable to locate an AnchorMapPositionId for {anchor.trackableId} and will be excluded from the list to delete.");
}
}
return UpdateExpirationForStoredSpatialAnchor(anchorMapPositionIds, expiration);
}
private void AnchorsStoragePlayerLoop()
{
if(!startedSpatialAnchorStorage)
{
unsafe
{
var createInfo = new XrSpatialAnchorsCreateStorageInfo
{
Type = XrSpatialAnchorsStorageStructTypes.XrTypeSpatialAnchorsCreateStorageInfo
};
XrResult createResult = spatialAnchorsStorageNativeFunctions.XrCreateSpatialAnchorsStorage(AppSession, &createInfo, out storageHandle);
if (!Utils.DidXrCallSucceed(createResult, nameof(spatialAnchorsStorageNativeFunctions.XrCreateSpatialAnchorsStorage)))
{
return;
}
startedSpatialAnchorStorage = true;
}
}
if(activeSubsystem == null)
{
var activeLoader = XRGeneralSettings.Instance.Manager.activeLoader;
activeSubsystem = activeLoader.GetLoadedSubsystem() as MLXrAnchorSubsystem;
if (activeSubsystem == null) return;
}
if (pendingStorageAnchors.Count > 0)
{
HashSet readyStorageAnchors = new HashSet();
HashSet failedStorageAnchors = new HashSet();
foreach (ulong future in pendingStorageAnchors)
{
spatialAnchorsStorageNativeFunctions.PollFuture(future, out XrFutureState pollState, out XrResult futureResult);
if (futureResult == (XrResult)MLXrResult.FutureInvalidEXT)
{
failedStorageAnchors.Add(future);
Debug.LogError($"CreateSpatialAnchorsFromStorage request failed with an invalid XrFuture.");
continue;
}
if(pollState == XrFutureState.Ready)
{
readyStorageAnchors.Add(future);
}
}
pendingStorageAnchors.ExceptWith(failedStorageAnchors);
foreach (ulong failedFuture in failedStorageAnchors)
{
pendingStorageAnchorsData.Remove(failedFuture);
}
pendingStorageAnchors.ExceptWith(readyStorageAnchors);
unsafe
{
if (readyStorageAnchors.Count > 0)
{
foreach (ulong future in readyStorageAnchors)
{
int count = pendingStorageAnchorsData[future].Count;
NativeArray allSpaces = new NativeArray(count, Allocator.Temp);
var completionInfo = new XrCreateSpatialAnchorsCompletion
{
Type = XrSpatialAnchorsStructTypes.XrTypeCreateSpatialAnchorsCompletion,
SpaceCount = (uint)count,
Spaces = (ulong*)allSpaces.GetUnsafePtr()
};
XrResult resultCode = anchorsFeature.SpatialAnchorsNativeFunctions.XrCreateSpatialAnchorsComplete(AppSession, future, out completionInfo);
if (!Utils.DidXrCallSucceed(resultCode, nameof(anchorsFeature.SpatialAnchorsNativeFunctions.XrCreateSpatialAnchorsComplete)))
{
pendingStorageAnchorsData.Remove(future);
continue;
}
XrPose[] poses = locateSpacesNativeFunctions.LocateSpaces(AppSpace, NextPredictedDisplayTime, AppSession, allSpaces.ToArray());
for (int i = 0; i < count; i++)
{
Pose unityPose = XrPose.GetUnityPose(poses[i]);
ulong anchorId = allSpaces[i];
string anchorMapPositionId = pendingStorageAnchorsData[future].Uuid[i];
activeSubsystem.AddStorageAnchorToSubsystem(unityPose, anchorId, anchorMapPositionId);
OnCreationCompleteFromStorage?.Invoke(unityPose, anchorId, anchorMapPositionId, completionInfo.FutureResult);
}
pendingStorageAnchorsData.Remove(future);
}
}
}
}
if (pendingPublishRequests.Count > 0)
{
HashSet readyPublishAnchors = new HashSet();
HashSet failedPublishAnchors = new HashSet();
foreach (ulong future in pendingPublishRequests)
{
spatialAnchorsStorageNativeFunctions.PollFuture(future, out XrFutureState pollState, out XrResult futureResult);
if (futureResult == (XrResult)MLXrResult.FutureInvalidEXT)
{
failedPublishAnchors.Add(future);
Debug.LogError($"PublishSpatialAnchorsToStorage request failed with an invalid XrFuture.");
continue;
}
if (pollState == XrFutureState.Ready)
{
readyPublishAnchors.Add(future);
}
}
pendingPublishRequests.ExceptWith(failedPublishAnchors);
foreach (ulong failedFuture in failedPublishAnchors)
{
pendingStorageAnchorsPublishData.Remove(failedFuture);
}
pendingPublishRequests.ExceptWith(readyPublishAnchors);
unsafe
{
if(readyPublishAnchors.Count > 0)
{
foreach (ulong future in readyPublishAnchors)
{
int count = pendingStorageAnchorsPublishData[future].Count;
using NativeArray completedUuids = new NativeArray(count, Allocator.Temp);
var completionInfo = new XrSpatialAnchorsPublishCompletion
{
Type = XrSpatialAnchorsStorageStructTypes.XrTypeSpatialAnchorsPublishCompletion,
UuidCount = (uint)count,
Uuids = (XrUUID*)completedUuids.GetUnsafePtr()
};
XrResult resultCode = spatialAnchorsStorageNativeFunctions.XrPublishSpatialAnchorsComplete(storageHandle, future, out completionInfo);
if (!Utils.DidXrCallSucceed(resultCode, nameof(spatialAnchorsStorageNativeFunctions.XrPublishSpatialAnchorsComplete)))
{
pendingStorageAnchorsPublishData.Remove(future);
continue;
}
if (completedUuids.Length != pendingStorageAnchorsPublishData[future].Count)
{
Debug.LogError($"PublishSpatialAnchorsToStorage completed with an Unexpected number of results.");
}
for (int i = 0; i < completedUuids.Length; i++)
{
ulong anchorId = pendingStorageAnchorsPublishData[future].AnchorIds[i];
string anchorMapPositionId = completedUuids[i].ToString();
activeSubsystem.PublishLocalAnchor(anchorId, anchorMapPositionId);
OnPublishComplete?.Invoke(anchorId, anchorMapPositionId);
}
pendingStorageAnchorsPublishData.Remove(future);
}
}
}
}
if(pendingQueries.Count > 0)
{
HashSet readyQueryAnchors = new HashSet();
HashSet failedQueryAnchors = new HashSet();
foreach (ulong future in pendingQueries)
{
spatialAnchorsStorageNativeFunctions.PollFuture(future, out XrFutureState pollState, out XrResult futureResult);
if (futureResult == (XrResult)MLXrResult.FutureInvalidEXT)
{
failedQueryAnchors.Add(future);
Debug.LogError($"QueryStoredSpatialAnchors request failed with an invalid XrFuture.");
continue;
}
if (pollState == XrFutureState.Ready)
{
readyQueryAnchors.Add(future);
}
}
pendingQueries.ExceptWith(failedQueryAnchors);
pendingQueries.ExceptWith(readyQueryAnchors);
unsafe
{
if (readyQueryAnchors.Count > 0)
{
foreach (ulong future in readyQueryAnchors)
{
var completion = new XrSpatialAnchorsQueryCompletion
{
Type = XrSpatialAnchorsStorageStructTypes.XrTypeSpatialAnchorsQueryCompletion,
UuidCapacityInput = 0,
Uuids = default
};
XrResult resultCode = spatialAnchorsStorageNativeFunctions.XrQuerySpatialAnchorsComplete(storageHandle, future, out completion);
if (!Utils.DidXrCallSucceed(resultCode, nameof(spatialAnchorsStorageNativeFunctions.XrQuerySpatialAnchorsComplete)))
{
continue;
}
uint count = completion.UuidCountOutput;
List returnedUuids = new List();
if (count > 0)
{
using NativeArray completedUuids = new NativeArray((int)count, Allocator.Temp);
completion.UuidCapacityInput = count;
completion.Uuids = (XrUUID*)completedUuids.GetUnsafePtr();
resultCode = spatialAnchorsStorageNativeFunctions.XrQuerySpatialAnchorsComplete(storageHandle, future, out completion);
if (!Utils.DidXrCallSucceed(resultCode, nameof(spatialAnchorsStorageNativeFunctions.XrQuerySpatialAnchorsComplete)))
{
continue;
}
foreach (XrUUID uuid in completedUuids)
{
returnedUuids.Add(uuid.ToString());
}
}
OnQueryComplete?.Invoke(returnedUuids);
}
}
}
}
if(pendingDeleteRequests.Count > 0)
{
HashSet readyDeleteAnchors = new HashSet();
HashSet failedDeleteAnchors = new HashSet();
foreach (ulong future in pendingDeleteRequests)
{
spatialAnchorsStorageNativeFunctions.PollFuture(future, out XrFutureState pollState, out XrResult futureResult);
if (futureResult == (XrResult)MLXrResult.FutureInvalidEXT)
{
failedDeleteAnchors.Add(future);
Debug.LogError($"DeleteStoredSpatialAnchor request failed with an invalid XrFuture.");
continue;
}
if (pollState == XrFutureState.Ready)
{
readyDeleteAnchors.Add(future);
}
}
pendingDeleteRequests.ExceptWith(failedDeleteAnchors);
foreach (ulong failedFuture in failedDeleteAnchors)
{
pendingStorageAnchorsDeleteData.Remove(failedFuture);
}
pendingDeleteRequests.ExceptWith(readyDeleteAnchors);
unsafe
{
if(readyDeleteAnchors.Count > 0)
{
foreach (ulong future in readyDeleteAnchors)
{
var completion = new XrSpatialAnchorsDeleteCompletion
{
Type = XrSpatialAnchorsStorageStructTypes.XrTypeSpatialAnchorsDeleteCompletion
};
XrResult resultCode = spatialAnchorsStorageNativeFunctions.XrDeleteSpatialAnchorsComplete(storageHandle, future, out completion);
if (!Utils.DidXrCallSucceed(resultCode, nameof(spatialAnchorsStorageNativeFunctions.XrDeleteSpatialAnchorsComplete)))
{
pendingStorageAnchorsDeleteData.Remove(future);
continue;
}
if (Utils.DidXrCallSucceed(completion.FutureResult, nameof(spatialAnchorsStorageNativeFunctions.XrDeleteSpatialAnchorsComplete)))
{
activeSubsystem.DeleteStoredAnchors(pendingStorageAnchorsDeleteData[future].Uuid);
OnDeletedComplete?.Invoke(pendingStorageAnchorsDeleteData[future].Uuid);
}
pendingStorageAnchorsDeleteData.Remove(future);
}
}
}
}
if(pendingUpdateRequests.Count > 0)
{
HashSet readyUpdateAnchors = new HashSet();
HashSet failedUpdateAnchors = new HashSet();
foreach (ulong future in pendingUpdateRequests)
{
spatialAnchorsStorageNativeFunctions.PollFuture(future, out XrFutureState pollState, out XrResult futureResult);
if (futureResult == (XrResult)MLXrResult.FutureInvalidEXT)
{
failedUpdateAnchors.Add(future);
Debug.LogError($"UpdateExpirationForStoredSpatialAnchor request failed with an invalid XrFuture.");
continue;
}
if (pollState == XrFutureState.Ready)
{
readyUpdateAnchors.Add(future);
}
}
pendingUpdateRequests.ExceptWith(failedUpdateAnchors);
foreach (ulong failedFuture in failedUpdateAnchors)
{
pendingStorageAnchorsUpdateData.Remove(failedFuture);
}
pendingUpdateRequests.ExceptWith(readyUpdateAnchors);
unsafe
{
if (readyUpdateAnchors.Count > 0)
{
foreach (ulong future in readyUpdateAnchors)
{
var completion = new XrSpatialAnchorsUpdateExpirationCompletion
{
Type = XrSpatialAnchorsStorageStructTypes.XrTypeSpatialAnchorsUpdateExpirationCompletion
};
XrResult resultCode = spatialAnchorsStorageNativeFunctions.XrUpdateSpatialAnchorsExpirationComplete(storageHandle, future, out completion);
if (!Utils.DidXrCallSucceed(resultCode, nameof(spatialAnchorsStorageNativeFunctions.XrUpdateSpatialAnchorsExpirationComplete)))
{
pendingStorageAnchorsUpdateData.Remove(future);
continue;
}
if (Utils.DidXrCallSucceed(completion.FutureResult, nameof(spatialAnchorsStorageNativeFunctions.XrUpdateSpatialAnchorsExpirationComplete)))
{
OnUpdateExpirationCompleted?.Invoke(pendingStorageAnchorsUpdateData[future].Uuid);
}
pendingStorageAnchorsUpdateData.Remove(future);
}
}
}
}
}
protected override void OnSessionDestroy(ulong xrSession)
{
if (startedSpatialAnchorStorage)
{
unsafe
{
XrResult createResult = spatialAnchorsStorageNativeFunctions.XrDestroySpatialAnchorsStorage(storageHandle);
if (Utils.DidXrCallSucceed(createResult, nameof(spatialAnchorsStorageNativeFunctions.XrDestroySpatialAnchorsStorage)))
{
startedSpatialAnchorStorage = false;
}
}
}
base.OnSessionDestroy(AppSession);
}
#if UNITY_EDITOR
protected override IEnumerable DependsOn => base.DependsOn.Append(typeof(MagicLeapSpatialAnchorsFeature));
#endif
}
}