Persistent anchors
AR Foundation 6 introduces an API for persistent anchors that enables you to save anchors in an AR session and load them in subsequent AR sessions. You can use this API to persist the state of your app across multiple sessions in the same physical space. The following sections explain how to use the persistent anchors API.
The Anchors sample scene shows you how to use the persistent anchor API on supported devices
Important
The AR Foundation persistent anchor API is a wrapper for persistent anchor APIs on multiple platforms. Due to the limitations of all supported platforms, you can't save an anchor on one platform and load it on another platform. Refer to the provider plug-in documentation for your target platforms to understand any additional platform-specific details.
Check support
The AR Foundation persistent anchor API consists of five optional features of the anchor subsystem:
- Save anchor
- Load anchor
- Erase anchor
- Get saved anchor IDs
- Async cancellation
Refer to the Optional features support table to learn which provider plug-ins support these features, and how to query for this information in C# scripts. The following sections explain each optional feature in more detail, including example code.
Save anchor
The save operation takes an anchor as input, saves that anchor to persistent storage, and returns a persistent anchor GUID that you can use later to load or erase the anchor.
Note
Some AR platforms may save anchors to the device's local disk, while others may save them to a cloud storage location associated with your app. Refer to the provider plug-in documentation for your target platforms to understand the implementation details of how anchors are saved.
To save an anchor, use ARAnchorManager.TrySaveAnchorAsync as shown in the following code example:
async void SaveAnchorAsync(ARAnchorManager manager, ARAnchor anchor)
{
var result = await manager.TrySaveAnchorAsync(anchor);
if (result.status.IsError())
{
// handle error
return;
}
// Save this value, then use it as an input parameter
// to TryLoadAnchorAsync or TryEraseAnchorAsync
SerializableGuid guid = result.value;
}
Important
Keep track of the persistent anchor GUIDs returned to you by TrySaveAnchorAsync
. Not all platforms support the ability to get your saved persistent anchor GUIDs if you lose them.
Batch save anchors
You can save a batch of anchors with ARAnchorManager.TrySaveAnchorsAsync as shown in the following code example:
async void SaveAnchorsAsync(
ARAnchorManager manager,
IEnumerable<ARAnchor> anchors)
{
var results = new List<ARSaveOrLoadAnchorResult>();
await manager.TrySaveAnchorsAsync(anchors, results);
foreach (var saveAnchorResult in results)
{
if (saveAnchorResult.resultStatus.IsSuccess())
{
// Save the `savedAnchorGuid`s stored in `saveAnchorResults`,
// then use them as input for TryLoadAnchorsAsync or
// `TryEraseAnchorsAsync`.
}
else
{
// Anchor failed to save. Handle error.
}
}
}
Important
Keep track of the persistent anchor GUIDs that are populated in saveAnchorResults.savedAnchorGuid
. Not all platforms support the ability to get your saved persistent anchor GUIDs if you lose them.
The AR Foundation Samples GitHub repository contains example code that you can use to save your persistent anchor GUIDs to Unity's Application.persistentDataPath, allowing you to quit your app and then load or erase your saved anchors in subsequent sessions. Any files created by your app and saved to Application.persistentDataPath
will be lost if your app is uninstalled.
By default, batch save anchors sequentially calls ARAnchorManager.TrySaveAnchorAsync
on the list of anchors passed in. Platforms can override this behavior with custom implementations for batch save anchors. Refer to your platform's documentation to understand platform specific implementation details.
Load anchor
The load operation takes a persistent anchor GUID returned by Save anchor as input, retrieves the associated anchor from persistent storage, and returns a newly created anchor. On the AR Anchor Manager component's next Update step, that anchor will be reported as added.
To load an anchor, use ARAnchorManager.TryLoadAnchorAsync as shown in the following code example:
async void LoadAnchorAsync(ARAnchorManager manager, SerializableGuid guid)
{
var result = await manager.TryLoadAnchorAsync(guid);
if (result.status.IsError())
{
// handle error
return;
}
// You can use this anchor as soon as it's returned to you.
ARAnchor anchor = result.value;
}
Batch load anchors
You can load a batch of anchors with ARAnchorManager.TryLoadAnchorsAsync as shown in the following code example:
async void LoadAnchorsAsync(
ARAnchorManager manager,
IEnumerable<SerializableGuid> savedAnchorGuids)
{
var results = new List<ARSaveOrLoadAnchorResult>();
await manager.TryLoadAnchorsAsync(
savedAnchorGuids,
results,
OnIncrementalResultsAvailable);
foreach (var loadAnchorResult in results)
{
if (loadAnchorResult.resultStatus.IsSuccess())
{
// Anchor with results.savedAnchorGuid was successfully loaded.
}
else
{
// Anchor with results.savedAnchorGuid failed to load.
}
}
}
void OnIncrementalResultsAvailable(ReadOnlyListSpan<ARSaveOrLoadAnchorResult> loadAnchorResults)
{
foreach (var loadAnchorResult in loadAnchorResults)
{
// You can use these anchors immediately without waiting for the
// entire batch to finish loading.
// loadAnchorResult.resultStatus.IsSuccess() will always be true
// for anchors passed to the incremental results callback.
ARAnchor loadedAnchor = loadAnchorResult.anchor;
}
}
The order in which anchors are loaded isn't guaranteed to match the order they were requested in. You can find the associated persistent anchor GUID of an anchor with LoadAnchorResult.savedAnchorGuid
.
By default, ARAnchorManager.TryLoadAnchorsAsync
sequentially calls ARAnchorManager.TryLoadAnchorAsync
on the list of saved persistent anchor GUIDs. Platforms can override this behavior with custom implementations for batch load anchors. Refer to your platform's documentation to understand platform specific implementation details.
Incremental load results
ARAnchorManager.TryLoadAnchorsAsync
accepts a callback that will be invoked each time a subset of requested anchors is loaded. This enables you to work with anchors as soon as they become available without waiting for the entire load request to complete. You should use the incremental results callback to ensure you're notified when an anchor has loaded before ARAnchorManager.trackablesChanged is raised. The final result from a batch load request isn't guaranteed to complete before ARAnchorManager.trackablesChanged
. To ignore the incremental results, pass null
for the callback.
By default, an incremental result is returned for each anchor as it's loaded. Platforms may override the default behavior and load groups of anchors incrementally allowing them to load some anchors immediately and some anchors more slowly, still guaranteeing to pass through the incremental results callback before ARAnchorManager.trackablesChanged
. Refer to your platform's documentation to understand platform specific implementation details.
The incremental results callback passes a ReadOnlyListSpan<ARSaveOrLoadAnchorResult> when invoked that provides a read-only slice of the List<ARSaveOrLoadAnchorResult>
output list passed into ARAnchorManager.TryLoadAnchorsAsync
.
When a request is made to load a batch of anchors, some anchors can fail to load. However, results reported during the incremental results callback will always have a success XRResultStatus. Only the final results stored in the List<ARSaveOrLoadAnchorResult>
output list after the load request completes can contain a result with an error StatusCode. Therefore, when working with the final results, you should check the resultStatus
of each result before using the anchor with resultStatus.IsSuccess() or resultStatus.IsError().
Erase anchor
The erase operation takes a persistent anchor GUID returned by Save anchor as input, erases that anchor from persistent storage, and returns a status indicating if the operation was successful.
Note
The save and erase operations only modify the persistent storage associated with an anchor, not the tracking state of the anchor. For instance, if you create an anchor, save it, then immediately erase it, the persistent storage associated with the anchor will be erased, but the anchor itself will not be removed from the scene.
To erase an anchor, use ARAnchorManager.TryEraseAnchorAsync as shown in the following code example:
async void EraseAnchorAsync(ARAnchorManager manager, SerializableGuid guid)
{
var status = await manager.TryEraseAnchorAsync(guid);
if (status.IsError())
{
// handle error
return;
}
// The anchor was successfully erased.
}
Batch erase anchors
You can erase a batch of anchors with ARAnchorManager.TryEraseAnchorsAsync as shown in the following code example:
async void EraseAnchorsAsync(
ARAnchorManager manager,
IEnumerable<SerializableGuid> savedAnchorGuids)
{
var eraseAnchorResults = new List<XREraseAnchorResult>();
await manager.TryEraseAnchorsAsync(savedAnchorGuids, eraseAnchorResults);
foreach (var eraseAnchorResult in eraseAnchorResults)
{
if (eraseAnchorResult.resultStatus.IsSuccess())
{
// anchor was successfully erased.
}
else
{
// anchor failed to erase
}
}
}
By default, batch erase anchors sequentially calls ARAnchorManager.TryEraseAnchorAsync
on the list of saved persistent anchor GUIDs. Platforms can override this behavior with custom implementations for batch erase anchors. Refer to your platform's documentation to understand platform specific implementation details.
Get saved anchor IDs
Some platforms support the ability to get a list of your currently saved anchors. If your app has successfully kept track of its state across usages of save anchor and erase anchor, you have no reason to use this API. However, if you lose track of your currently saved anchors for any reason, this API is a useful way to recover them, allowing you to subsequently load or erase your saved anchors.
The following example code demonstrates how to get saved anchor IDs:
async void GetSavedAnchorIdsAsync(ARAnchorManager manager)
{
// If you need to keep the saved anchor IDs longer than a frame, use
// Allocator.Persistent instead, then remember to Dispose the array.
var result = await manager.TryGetSavedAnchorIdsAsync(Allocator.Temp);
if (result.status.IsError())
{
// handle error
return;
}
// Do something with the saved anchor IDs
NativeArray<SerializableGuid> anchorIds = result.value;
}
Async cancellation
AR Foundation's persistent anchors API is entirely asynchronous. If your target platform supports the ability to cancel async operations in progress, you can use the CancellationToken input parameter of the other persistent anchor methods. Otherwise, this input parameter is ignored on platforms that do not support cancellation.
The following example code demonstrates how to cancel an async operation:
void AsyncCancellation(ARAnchorManager manager)
{
// Create a CancellationTokenSource to serve our CancellationToken
var cts = new CancellationTokenSource();
// Use one of the other methods in the persistent anchor API
var awaitable = manager.TryGetSavedAnchorIdsAsync(Allocator.Temp, cts.Token);
// Cancel the async operation before it completes
cts.Cancel();
}