Files
plugin-library/Assets/99.imdk_unity/Runtime/Scripts/XR/ImmersalSession.cs

375 lines
9.5 KiB
C#
Raw Normal View History

2025-06-19 10:56:43 +08:00
/*===============================================================================
Copyright (C) 2024 Immersal - Part of Hexagon. All Rights Reserved.
This file is part of the Immersal SDK.
The Immersal SDK cannot be copied, distributed, or made available to
third-parties for commercial purposes without written permission of Immersal Ltd.
Contact sales@immersal.com for licensing requests.
===============================================================================*/
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Serialization;
using Object = UnityEngine.Object;
namespace Immersal.XR
{
public class ImmersalSession : MonoBehaviour, IImmersalSession
{
[Tooltip("Start Session at app startup")] [SerializeField]
private bool m_AutoStart = true;
[Tooltip("Seconds between continuous updates")] [SerializeField]
private float m_SessionUpdateInterval = 2.0f;
[Tooltip("Try to localize at maximum speed at app startup / resume")] [SerializeField]
private bool m_BurstMode = true;
[Tooltip("Number of successful localizations to turn off burst mode")] [SerializeField]
private int m_BurstSuccessCount = 10;
[Tooltip("Time limit for burst mode in seconds")] [SerializeField]
private float m_BurstTimeLimit = 15f;
[Tooltip("Reset stats and ARSpace filters on application pause")] [SerializeField]
private bool m_ResetOnPause = true;
[Tooltip("Restart session automatically after a reset")] [SerializeField]
private bool m_RestartOnReset = true;
[Tooltip("Process component results in a data processing chain")] [SerializeField]
private bool m_ProcessData = false;
[SerializeField]
[Interface(typeof(IDataProcessor<SessionData>))]
private Object[] m_SessionDataProcessors;
public IDataProcessor<SessionData>[] SessionDataProcessors =>
m_SessionDataProcessors.OfType<IDataProcessor<SessionData>>().ToArray();
public bool AutoStart
{
get => m_AutoStart;
set => m_AutoStart = value;
}
public bool BurstMode
{
get { return m_BurstMode; }
set { SetBurstMode(value); }
}
public bool ProcessData
{
get => m_ProcessData;
set => m_ProcessData = value;
}
public float SessionUpdateInterval
{
get => m_SessionUpdateInterval;
set => m_SessionUpdateInterval = value;
}
public int BurstSuccessCount
{
get => m_BurstSuccessCount;
set => m_BurstSuccessCount = value;
}
public float BurstTimeLimit
{
get => m_BurstTimeLimit;
set => m_BurstTimeLimit = value;
}
public bool RestartOnReset
{
get => m_RestartOnReset;
set => m_RestartOnReset = value;
}
private void SetBurstMode(bool on)
{
m_BurstStartTime = Time.unscaledTime;
m_BurstModeActive = on;
}
#region Events
public UnityEvent OnPause;
public UnityEvent OnResume;
public UnityEvent OnReset;
#endregion
private ImmersalSDK sdk;
private float m_BurstStartTime = 0.0f;
private bool m_BurstModeActive = false;
private float m_LastUpdateTime;
private bool m_SessionIsRunning = false;
private bool m_RunningTasks = false;
private bool m_Paused = false;
private Task m_CurrentRunningTask;
private CancellationTokenSource m_CTS;
private IPlatformUpdateResult m_LatestPlatformUpdateResult = null;
private ILocalizationResults m_LatestLocalizationResults = null;
private DataProcessingChain<SessionData> m_SessionDataProcessingChain;
private void Start()
{
sdk = ImmersalSDK.Instance;
m_SessionDataProcessingChain = new DataProcessingChain<SessionData>(SessionDataProcessors);
if (!m_AutoStart)
return;
if (sdk.IsReady)
{
StartSession();
}
else
{
sdk.OnInitializationComplete.AddListener(StartSession);
}
}
public void StartSession()
{
if (m_SessionIsRunning)
{
if (m_Paused)
ResumeSession(); // Resume if paused
return;
}
if (!sdk.IsReady)
return;
ImmersalLogger.Log("Session starting", ImmersalLogger.LoggingLevel.Verbose);
m_SessionIsRunning = true;
SetBurstMode(BurstMode);
}
private void Update()
{
// update data processing chain
if (m_ProcessData)
m_SessionDataProcessingChain.UpdateChain();
// bail out conditionals
if (!m_SessionIsRunning || !sdk.IsReady || !MapManager.HasRegisteredMaps)
return;
float curTime = Time.unscaledTime;
// deactivate burst after enough success or certain time
if (sdk.TrackingStatus?.LocalizationSuccessCount >= m_BurstSuccessCount || curTime - m_BurstStartTime >= m_BurstTimeLimit)
{
SetBurstMode(false);
}
// check if update interval has passed or if we are bursting
bool updateIntervalOrBurst = curTime - m_LastUpdateTime >= m_SessionUpdateInterval
|| m_BurstModeActive;
// update conditional
bool doUpdate = updateIntervalOrBurst
&& !m_RunningTasks
&& !m_Paused;
if (doUpdate)
{
m_LastUpdateTime = curTime;
m_RunningTasks = true;
m_CTS = new CancellationTokenSource();
m_CurrentRunningTask = RunTasksAsync(m_ProcessData, m_CTS.Token);
}
}
private async Task RunTasksAsync(bool processData, CancellationToken cancellationToken)
{
try
{
// Update platform
m_LatestPlatformUpdateResult = await sdk.PlatformSupport.UpdatePlatform();
if (cancellationToken.IsCancellationRequested) { m_RunningTasks = false; return; }
if (m_LatestPlatformUpdateResult.Success)
{
// Localize
m_LatestLocalizationResults = await sdk.Localizer.Localize(m_LatestPlatformUpdateResult.CameraData);
if (cancellationToken.IsCancellationRequested) { m_RunningTasks = false; return; }
foreach (ILocalizationResult result in m_LatestLocalizationResults.Results)
{
if (result.Success)
{
// Check if map entry exists
if (MapManager.TryGetMapEntry(result.MapId, out MapEntry entry))
{
ImmersalLogger.Log($"Localized to map {entry.Map.mapId}-{entry.Map.mapName}", ImmersalLogger.LoggingLevel.Verbose);
ILocalizationResult r = result;
IPlatformUpdateResult p = m_LatestPlatformUpdateResult;
if (processData)
{
// Push new data to data processing chain
await m_SessionDataProcessingChain.ProcessNewData(new SessionData()
{
Entry = entry,
PlatformResult = p,
LocalizationResult = r
});
SessionData processed = m_SessionDataProcessingChain.GetCurrentData();
entry = processed.Entry;
r = processed.LocalizationResult;
p = processed.PlatformResult;
}
// Update SceneUpdater
if (cancellationToken.IsCancellationRequested) { m_RunningTasks = false; return; }
await sdk.SceneUpdater.UpdateScene(entry, p.CameraData, r);
}
else
{
ImmersalLogger.LogError("Localization result does not match with any registered map.");
}
}
}
}
else
{
// Localization never ran due to failed platform update
// so we need to invalidate previous results.
m_LatestLocalizationResults = new LocalizationResults
{
Results = Array.Empty<ILocalizationResult>()
};
}
// Update tracking status
sdk.TrackingAnalyzer.Analyze(m_LatestPlatformUpdateResult.Status, m_LatestLocalizationResults);
}
catch (Exception e)
{
ImmersalLogger.LogError($"Session task error: {e.Message}. Stopping session.");
m_SessionIsRunning = false;
}
finally
{
m_RunningTasks = false;
}
}
// Convenience method
public async Task LocalizeOnce()
{
if (m_CurrentRunningTask != null)
await m_CurrentRunningTask;
m_LastUpdateTime = Time.unscaledTime;
m_RunningTasks = true;
m_CTS = new CancellationTokenSource();
try
{
m_CurrentRunningTask = RunTasksAsync(m_ProcessData, m_CTS.Token);
}
catch (Exception e)
{
ImmersalLogger.LogError(e.Message);
m_SessionIsRunning = false;
}
}
// Note: MonoBehaviour.OnApplicationPause is called as a GameObject starts. The call is made after Awake.
private void OnApplicationPause(bool paused)
{
if (paused)
{
PauseSession();
}
else
{
ResumeSession();
}
}
public async void PauseSession()
{
if (!m_SessionIsRunning)
return;
if (m_ResetOnPause)
await ResetSession();
m_Paused = true;
OnPause?.Invoke();
}
public void ResumeSession()
{
if (!m_SessionIsRunning)
return;
m_Paused = false;
SetBurstMode(BurstMode);
OnResume?.Invoke();
}
public void TriggerResetSession()
{
ResetSession();
}
public async Task ResetSession()
{
ImmersalLogger.Log("Resetting session");
await StopSession();
await m_SessionDataProcessingChain.ResetProcessors();
OnReset?.Invoke();
if (m_RestartOnReset)
StartSession();
}
public async Task StopSession(bool cancelRunningTask = true)
{
// Send cancellation token to abort running task
if (cancelRunningTask)
m_CTS?.Cancel();
// Wait for running task to finish
if (m_CurrentRunningTask != null)
await m_CurrentRunningTask;
m_SessionIsRunning = false;
m_RunningTasks = false;
m_LatestLocalizationResults = null;
m_LatestPlatformUpdateResult = null;
m_BurstModeActive = false;
m_BurstStartTime = 0.0f;
m_LastUpdateTime = 0f;
m_Paused = false;
ImmersalLogger.Log("Session stopped");
}
}
public class SessionData
{
public MapEntry Entry;
public IPlatformUpdateResult PlatformResult;
public ILocalizationResult LocalizationResult;
}
}