497 lines
22 KiB
C#
497 lines
22 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
using UnityEditor;
|
|
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
using UnityEngine.UIElements;
|
|
using UnityEngine.Serialization;
|
|
using UnityEngine.XR.Management;
|
|
|
|
[assembly: InternalsVisibleTo("Unity.XR.Management.Tests")]
|
|
[assembly: InternalsVisibleTo("Unity.XR.Management.EditorTests")]
|
|
namespace UnityEngine.XR.Management
|
|
{
|
|
/// <summary>
|
|
/// Class to handle active loader and subsystem management for XR. This class is to be added as a
|
|
/// ScriptableObject asset in your project and should only be referenced by the <see cref="XRGeneralSettings"/>
|
|
/// instance for its use.
|
|
///
|
|
/// Given a list of loaders, it will attempt to load each loader in the given order. The first
|
|
/// loader that is successful wins and all remaining loaders are ignored. The loader
|
|
/// that succeeds is accessible through the <see cref="activeLoader"/> property on the manager.
|
|
///
|
|
/// Depending on configuration the <see cref="XRManagerSettings"/> instance will automatically manage the active loader
|
|
/// at correct points in the application lifecycle. The user can override certain points in the active loader lifecycle
|
|
/// and manually manage them by toggling the <see cref="automaticLoading"/> and <see cref="automaticRunning"/>
|
|
/// properties. Disabling <see cref="automaticLoading"/> implies the the user is responsible for the full lifecycle
|
|
/// of the XR session normally handled by the <see cref="XRManagerSettings"/> instance. Toggling this to false also toggles
|
|
/// <see cref="automaticRunning"/> false.
|
|
///
|
|
/// Disabling <see cref="automaticRunning"/> only implies that the user is responsible for starting and stopping
|
|
/// the <see cref="activeLoader"/> through the <see cref="StartSubsystems"/> and <see cref="StopSubsystems"/> APIs.
|
|
///
|
|
/// Automatic lifecycle management is executed as follows
|
|
///
|
|
/// * Runtime Initialize -> <see cref="InitializeLoader"/>. The loader list will be iterated over and the first successful loader will be set as the active loader.
|
|
/// * Start -> <see cref="StartSubsystems"/>. Ask the active loader to start all subsystems.
|
|
/// * OnDisable -> <see cref="StopSubsystems"/>. Ask the active loader to stop all subsystems.
|
|
/// * OnDestroy -> <see cref="DeinitializeLoader"/>. Deinitialize and remove the active loader.
|
|
/// </summary>
|
|
public sealed class XRManagerSettings : ScriptableObject
|
|
{
|
|
[HideInInspector]
|
|
bool m_InitializationComplete = false;
|
|
|
|
#pragma warning disable 414
|
|
// This property is only used by the scriptable object editing part of the system and as such no one
|
|
// directly references it. Have to manually disable the console warning here so that we can
|
|
// get a clean console report.
|
|
[HideInInspector]
|
|
[SerializeField]
|
|
bool m_RequiresSettingsUpdate = false;
|
|
#pragma warning restore 414
|
|
|
|
[SerializeField]
|
|
[Tooltip("Determines if the XR Manager instance is responsible for creating and destroying the appropriate loader instance.")]
|
|
[FormerlySerializedAs("AutomaticLoading")]
|
|
bool m_AutomaticLoading = false;
|
|
|
|
/// <summary>
|
|
/// Get and set Automatic Loading state for this manager. When this is true, the manager will automatically call
|
|
/// <see cref="InitializeLoader"/> and <see cref="DeinitializeLoader"/> for you. When false <see cref="automaticRunning"/>
|
|
/// is also set to false and remains that way. This means that disabling automatic loading disables all automatic behavior
|
|
/// for the manager.
|
|
/// </summary>
|
|
public bool automaticLoading
|
|
{
|
|
get { return m_AutomaticLoading; }
|
|
set { m_AutomaticLoading = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
[Tooltip("Determines if the XR Manager instance is responsible for starting and stopping subsystems for the active loader instance.")]
|
|
[FormerlySerializedAs("AutomaticRunning")]
|
|
bool m_AutomaticRunning = false;
|
|
|
|
/// <summary>
|
|
/// Get and set automatic running state for this manager. When set to true the manager will call <see cref="StartSubsystems"/>
|
|
/// and <see cref="StopSubsystems"/> APIs at appropriate times. When set to false, or when <see cref="automaticLoading"/> is false
|
|
/// then it is up to the user of the manager to handle that same functionality.
|
|
/// </summary>
|
|
public bool automaticRunning
|
|
{
|
|
get { return m_AutomaticRunning; }
|
|
set { m_AutomaticRunning = value; }
|
|
}
|
|
|
|
|
|
[SerializeField]
|
|
[Tooltip("List of XR Loader instances arranged in desired load order.")]
|
|
[FormerlySerializedAs("Loaders")]
|
|
List<XRLoader> m_Loaders = new List<XRLoader>();
|
|
|
|
// Maintains a list of registered loaders that is immutable at runtime.
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
HashSet<XRLoader> m_RegisteredLoaders = new HashSet<XRLoader>();
|
|
|
|
/// <summary>
|
|
/// List of loaders currently managed by this XR Manager instance.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Modifying the list of loaders at runtime is undefined behavior and could result in a crash or memory leak.
|
|
/// Use <see cref="activeLoaders"/> to retrieve the currently ordered list of loaders. If you need to mutate
|
|
/// the list at runtime, use <see cref="TryAddLoader"/>, <see cref="TryRemoveLoader"/>, and
|
|
/// <see cref="TrySetLoaders"/>.
|
|
/// </remarks>
|
|
[Obsolete("'XRManagerSettings.loaders' property is obsolete. Use 'XRManagerSettings.activeLoaders' instead to get a list of the current loaders.")]
|
|
public List<XRLoader> loaders
|
|
{
|
|
get { return m_Loaders; }
|
|
#if UNITY_EDITOR
|
|
set { m_Loaders = value; }
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// A shallow copy of the list of loaders currently managed by this XR Manager instance.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This property returns a read only list. Any changes made to the list itself will not affect the list
|
|
/// used by this XR Manager instance. To mutate the list of loaders currently managed by this instance,
|
|
/// use <see cref="TryAddLoader"/>, <see cref="TryRemoveLoader"/>, and/or <see cref="TrySetLoaders"/>.
|
|
/// </remarks>
|
|
public IReadOnlyList<XRLoader> activeLoaders => m_Loaders;
|
|
|
|
/// <summary>
|
|
/// Read only boolean letting us know if initialization is completed. Because initialization is
|
|
/// handled as a Coroutine, people taking advantage of the auto-lifecycle management of XRManager
|
|
/// will need to wait for init to complete before checking for an ActiveLoader and calling StartSubsystems.
|
|
/// </summary>
|
|
public bool isInitializationComplete
|
|
{
|
|
get { return m_InitializationComplete; }
|
|
}
|
|
|
|
///<summary>
|
|
/// Return the current singleton active loader instance.
|
|
///</summary>
|
|
[HideInInspector]
|
|
public XRLoader activeLoader { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Return the current active loader, cast to the requested type. Useful shortcut when you need
|
|
/// to get the active loader as something less generic than XRLoader.
|
|
/// </summary>
|
|
///
|
|
/// <typeparam name="T">Requested type of the loader</typeparam>
|
|
///
|
|
/// <returns>The active loader as requested type, or null.</returns>
|
|
public T ActiveLoaderAs<T>() where T : XRLoader
|
|
{
|
|
return activeLoader as T;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Iterate over the configured list of loaders and attempt to initialize each one. The first one
|
|
/// that succeeds is set as the active loader and initialization immediately terminates.
|
|
///
|
|
/// When complete <see cref="isInitializationComplete"/> will be set to true. This will mark that it is safe to
|
|
/// call other parts of the API. This does not guarantee that init successfully created a loader. For that
|
|
/// you need to check that ActiveLoader is not null.
|
|
///
|
|
/// Note that there can only be one active loader. Any attempt to initialize a new active loader with one
|
|
/// already set will cause a warning to be logged and immediate exit of this function.
|
|
///
|
|
/// This method is synchronous and on return all state should be immediately checkable.
|
|
///
|
|
/// <b>If manual initialization of XR is being done, this method can not be called before Start completes
|
|
/// as it depends on graphics initialization within Unity completing.</b>
|
|
/// </summary>
|
|
public void InitializeLoaderSync()
|
|
{
|
|
if (activeLoader != null)
|
|
{
|
|
Debug.LogWarning(
|
|
"XR Management has already initialized an active loader in this scene." +
|
|
" Please make sure to stop all subsystems and deinitialize the active loader before initializing a new one.");
|
|
return;
|
|
}
|
|
|
|
foreach (var loader in currentLoaders)
|
|
{
|
|
if (loader != null)
|
|
{
|
|
if (CheckGraphicsAPICompatibility(loader) && loader.Initialize())
|
|
{
|
|
activeLoader = loader;
|
|
m_InitializationComplete = true;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
activeLoader = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Iterate over the configured list of loaders and attempt to initialize each one. The first one
|
|
/// that succeeds is set as the active loader and initialization immediately terminates.
|
|
///
|
|
/// When complete <see cref="isInitializationComplete"/> will be set to true. This will mark that it is safe to
|
|
/// call other parts of the API. This does not guarantee that init successfully created a loader. For that
|
|
/// you need to check that ActiveLoader is not null.
|
|
///
|
|
/// Note that there can only be one active loader. Any attempt to initialize a new active loader with one
|
|
/// already set will cause a warning to be logged and immediate exit of this function.
|
|
///
|
|
/// Iteration is done asynchronously and this method must be called within the context of a Coroutine.
|
|
///
|
|
/// <b>If manual initialization of XR is being done, this method can not be called before Start completes
|
|
/// as it depends on graphics initialization within Unity completing.</b>
|
|
/// </summary>
|
|
///
|
|
/// <returns>Enumerator marking the next spot to continue execution at.</returns>
|
|
public IEnumerator InitializeLoader()
|
|
{
|
|
if (activeLoader != null)
|
|
{
|
|
Debug.LogWarning(
|
|
"XR Management has already initialized an active loader in this scene." +
|
|
" Please make sure to stop all subsystems and deinitialize the active loader before initializing a new one.");
|
|
yield break;
|
|
}
|
|
|
|
foreach (var loader in currentLoaders)
|
|
{
|
|
if (loader != null)
|
|
{
|
|
if (CheckGraphicsAPICompatibility(loader) && loader.Initialize())
|
|
{
|
|
activeLoader = loader;
|
|
m_InitializationComplete = true;
|
|
yield break;
|
|
}
|
|
}
|
|
|
|
yield return null;
|
|
}
|
|
|
|
activeLoader = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to append the given loader to the list of loaders at the given index.
|
|
/// </summary>
|
|
/// <param name="loader">
|
|
/// The <see cref="XRLoader"/> to be added to this manager's instance of loaders.
|
|
/// </param>
|
|
/// <param name="index">
|
|
/// The index at which the given <see cref="XRLoader"/> should be added. If you set a negative or otherwise
|
|
/// invalid index, the loader will be appended to the end of the list.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the loader is not a duplicate and was added to the list successfully, <c>false</c>
|
|
/// otherwise.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// This method behaves differently in the Editor and during runtime/Play mode. While your app runs in the Editor and not in
|
|
/// Play mode, attempting to add an <see cref="XRLoader"/> will always succeed and register that loader's type
|
|
/// internally. Attempting to add a loader during runtime/Play mode will trigger a check to see whether a loader of
|
|
/// that type was registered. If the check is successful, the loader is added. If not, the loader is not added and the method
|
|
/// returns <c>false</c>.
|
|
/// </remarks>
|
|
public bool TryAddLoader(XRLoader loader, int index = -1)
|
|
{
|
|
if (loader == null || currentLoaders.Contains(loader))
|
|
return false;
|
|
|
|
#if UNITY_EDITOR
|
|
if (!EditorApplication.isPlaying && !m_RegisteredLoaders.Contains(loader))
|
|
m_RegisteredLoaders.Add(loader);
|
|
#endif
|
|
if (!m_RegisteredLoaders.Contains(loader))
|
|
return false;
|
|
|
|
if (index < 0 || index >= currentLoaders.Count)
|
|
currentLoaders.Add(loader);
|
|
else
|
|
currentLoaders.Insert(index, loader);
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to remove the first instance of a given loader from the list of loaders.
|
|
/// </summary>
|
|
/// <param name="loader">
|
|
/// The <see cref="XRLoader"/> to be removed from this manager's instance of loaders.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the loader was successfully removed from the list, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// This method behaves differently in the Editor and during runtime/Play mode. During runtime/Play mode, the loader
|
|
/// will be removed with no additional side effects if it is in the list managed by this instance. While in the
|
|
/// Editor and not in Play mode, the loader will be removed if it exists and
|
|
/// it will be unregistered from this instance and any attempts to add it during
|
|
/// runtime/Play mode will fail. You can re-add the loader in the Editor while not in Play mode.
|
|
/// </remarks>
|
|
public bool TryRemoveLoader(XRLoader loader)
|
|
{
|
|
var removedLoader = true;
|
|
if (currentLoaders.Contains(loader))
|
|
removedLoader = currentLoaders.Remove(loader);
|
|
|
|
#if UNITY_EDITOR
|
|
if (!EditorApplication.isPlaying && !currentLoaders.Contains(loader))
|
|
m_RegisteredLoaders.Remove(loader);
|
|
#endif
|
|
|
|
return removedLoader;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to set the given loader list as the list of loaders managed by this instance.
|
|
/// </summary>
|
|
/// <param name="reorderedLoaders">
|
|
/// The list of <see cref="XRLoader"/>s to be managed by this manager instance.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the loader list was set successfully, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// This method behaves differently in the Editor and during runtime/Play mode. While in the Editor and not in
|
|
/// Play mode, any attempts to set the list of loaders will succeed without any additional checks. During
|
|
/// runtime/Play mode, the new loader list will be validated against the registered <see cref="XRLoader"/> types.
|
|
/// If any loaders exist in the list that were not registered at startup, the attempt will fail.
|
|
/// </remarks>
|
|
public bool TrySetLoaders(List<XRLoader> reorderedLoaders)
|
|
{
|
|
var originalLoaders = new List<XRLoader>(activeLoaders);
|
|
#if UNITY_EDITOR
|
|
if (!EditorApplication.isPlaying)
|
|
{
|
|
registeredLoaders.Clear();
|
|
currentLoaders.Clear();
|
|
foreach (var loader in reorderedLoaders)
|
|
{
|
|
if (!TryAddLoader(loader))
|
|
{
|
|
TrySetLoaders(originalLoaders);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
currentLoaders.Clear();
|
|
foreach (var loader in reorderedLoaders)
|
|
{
|
|
if (!TryAddLoader(loader))
|
|
{
|
|
currentLoaders = originalLoaders;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool CheckGraphicsAPICompatibility(XRLoader loader)
|
|
{
|
|
GraphicsDeviceType deviceType = SystemInfo.graphicsDeviceType;
|
|
List<GraphicsDeviceType> supportedDeviceTypes = loader.GetSupportedGraphicsDeviceTypes(false);
|
|
|
|
// To help with backward compatibility, if the compatibility list is empty we assume that it does not implement the GetSupportedGraphicsDeviceTypes method
|
|
// Therefore we revert to the previous behavior of building or starting the loader regardless of gfx api settings.
|
|
if (supportedDeviceTypes.Count > 0 && !supportedDeviceTypes.Contains(deviceType))
|
|
{
|
|
Debug.LogWarning(String.Format("The {0} does not support the initialized graphics device, {1}. Please change the preffered Graphics API in PlayerSettings. Attempting to start the next XR loader.", loader.name, deviceType.ToString()));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// If there is an active loader, this will request the loader to start all the subsystems that it
|
|
/// is managing.
|
|
///
|
|
/// You must wait for <see cref="isInitializationComplete"/> to be set to true prior to calling this API.
|
|
/// </summary>
|
|
public void StartSubsystems()
|
|
{
|
|
if (!m_InitializationComplete)
|
|
{
|
|
Debug.LogWarning(
|
|
"Call to StartSubsystems without an initialized manager." +
|
|
"Please make sure wait for initialization to complete before calling this API.");
|
|
return;
|
|
}
|
|
|
|
if (activeLoader != null)
|
|
{
|
|
activeLoader.Start();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// If there is an active loader, this will request the loader to stop all the subsystems that it
|
|
/// is managing.
|
|
///
|
|
/// You must wait for <see cref="isInitializationComplete"/> to be set to tru prior to calling this API.
|
|
/// </summary>
|
|
public void StopSubsystems()
|
|
{
|
|
if (!m_InitializationComplete)
|
|
{
|
|
Debug.LogWarning(
|
|
"Call to StopSubsystems without an initialized manager." +
|
|
"Please make sure wait for initialization to complete before calling this API.");
|
|
return;
|
|
}
|
|
|
|
if (activeLoader != null)
|
|
{
|
|
activeLoader.Stop();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// If there is an active loader, this function will deinitialize it and remove the active loader instance from
|
|
/// management. We will automatically call <see cref="StopSubsystems"/> prior to deinitialization to make sure
|
|
/// that things are cleaned up appropriately.
|
|
///
|
|
/// You must wait for <see cref="isInitializationComplete"/> to be set to tru prior to calling this API.
|
|
///
|
|
/// Upon return <see cref="isInitializationComplete"/> will be rest to false;
|
|
/// </summary>
|
|
public void DeinitializeLoader()
|
|
{
|
|
if (!m_InitializationComplete)
|
|
{
|
|
Debug.LogWarning(
|
|
"Call to DeinitializeLoader without an initialized manager." +
|
|
"Please make sure wait for initialization to complete before calling this API.");
|
|
return;
|
|
}
|
|
|
|
StopSubsystems();
|
|
if (activeLoader != null)
|
|
{
|
|
activeLoader.Deinitialize();
|
|
activeLoader = null;
|
|
}
|
|
|
|
m_InitializationComplete = false;
|
|
}
|
|
|
|
// Use this for initialization
|
|
void Start()
|
|
{
|
|
if (automaticLoading && automaticRunning)
|
|
{
|
|
StartSubsystems();
|
|
}
|
|
}
|
|
|
|
void OnDisable()
|
|
{
|
|
if (automaticLoading && automaticRunning)
|
|
{
|
|
StopSubsystems();
|
|
}
|
|
}
|
|
|
|
void OnDestroy()
|
|
{
|
|
if (automaticLoading)
|
|
{
|
|
DeinitializeLoader();
|
|
}
|
|
}
|
|
|
|
// To modify the list of loaders internally use `currentLoaders` as it will return a list reference rather
|
|
// than a shallow copy.
|
|
// TODO @davidmo 10/12/2020: remove this in next major version bump and make 'loaders' internal.
|
|
internal List<XRLoader> currentLoaders
|
|
{
|
|
get { return m_Loaders; }
|
|
set { m_Loaders = value; }
|
|
}
|
|
|
|
// To modify the set of registered loaders use `registeredLoaders` as it will return a reference to the
|
|
// hashset of loaders.
|
|
internal HashSet<XRLoader> registeredLoaders
|
|
{
|
|
get { return m_RegisteredLoaders; }
|
|
}
|
|
}
|
|
}
|