// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.XR.OpenXR; using UnityEngine.XR.OpenXR.Features; using UnityEngine.XR.OpenXR.NativeTypes; namespace Microsoft.MixedReality.OpenXR { internal abstract class OpenXRFeaturePlugin : OpenXRFeaturePlugin where TPlugin : OpenXRFeaturePlugin { private static OpenXRFeature m_feature; internal static TPlugin Feature => (TPlugin)m_feature; protected override void OnEnable() { // Important notes about this Feature reference initialization: // - Awake and OnEnable are called sequentially when this ScriptableObject is created, far earlier than when values such as the Feature singleton should be used. // - ScriptableObject::OnEnable always runs when the feature is supported, independent of whether or not the enabled value defined in OpenXRFeature is true. // - The reference must be refreshed in OnEnable, as Awake is not called on domain refresh (e.g. editing scripts while the editor is open). // - References to OpenXR Project Settings are avoided, as they may not be available (e.g. while regenerating the project settings asset). var featureInCurrentPlatform = OpenXRSettings.Instance.GetFeature(); if (featureInCurrentPlatform == this) { // C# scriptable object might be enabled for project settings in editor // that's meant for other platforms, and won't be used when running the app. // Only initialize those scriptable objects in the current platform. // This field will be refreshed upon app domain refresh. m_feature = this; } base.OnEnable(); } } internal abstract class OpenXRFeaturePlugin : OpenXRFeature, IOpenXRContext, ISubsystemPlugin { private List m_subsystemControllers = new List(); public ulong Instance { get; private set; } = 0; public ulong SystemId { get; private set; } = 0; public ulong Session { get; private set; } = 0; public bool IsSessionRunning { get; private set; } = false; public XrSessionState SessionState { get; private set; } = XrSessionState.Unknown; public ulong SceneOriginSpace { get; private set; } = 0; public event OpenXRContextEvent InstanceCreated; // after instance is created public event OpenXRContextEvent InstanceDestroying; // before instance is destroyed public event OpenXRContextEvent SessionCreated; // after session is created public event OpenXRContextEvent SessionDestroying; // before session is destroyed public event OpenXRContextEvent SessionBegun; // after session is begun public event OpenXRContextEvent SessionEnding; // before session is ended // Convert protected OpenXRFeature.xrGetInstanceProcAddr to internal visibility internal static IntPtr PFN_xrGetInstanceProcAddr => OpenXRFeature.xrGetInstanceProcAddr; protected override void OnEnable() { base.OnEnable(); OpenXRFeaturePluginManager.OnFeaturePluginInitializing(this); if (OpenXRFeaturePluginManager.NativeLibAvailable) { PluginEnvironmentSubsystem.InitializePlugin(); } } protected override IntPtr HookGetInstanceProcAddr(IntPtr func) { OpenXRFeaturePluginManager.InitializeOpenXRFeatureList(); if (OpenXRFeaturePluginManager.IsResponsibleForNativeLib(this)) { func = NativeLib.HookGetInstanceProcAddr(func); } return func; } protected void AddSubsystemController(SubsystemController subsystemController) { m_subsystemControllers.Add(subsystemController); } protected override void OnSubsystemCreate() { m_subsystemControllers.ForEach(controller => controller.OnSubsystemCreate(this)); } protected override void OnSubsystemStart() { if (OpenXRFeaturePluginManager.IsResponsibleForNativeLib(this)) { NativeLib.OnSubsystemsStarting(); } m_subsystemControllers.ForEach(controller => controller.OnSubsystemStart(this)); } protected override void OnSubsystemStop() { m_subsystemControllers.ForEach(controller => controller.OnSubsystemStop(this)); if (OpenXRFeaturePluginManager.IsResponsibleForNativeLib(this)) { NativeLib.OnSubsystemsStopped(); } } protected override void OnSubsystemDestroy() { m_subsystemControllers.ForEach(controller => controller.OnSubsystemDestroy(this)); } protected override bool OnInstanceCreate(ulong instance) { if (Instance != 0) { Debug.LogWarning("New instance was created without properly destroying the previous one."); } Instance = instance; string[] enabledExtensionNames = OpenXRRuntime.GetEnabledExtensions(); if (OpenXRFeaturePluginManager.IsResponsibleForNativeLib(this)) { NativeLib.OnInstanceCreated(instance, PFN_xrGetInstanceProcAddr, enabledExtensionNames, enabledExtensionNames.Length); } InstanceCreated?.Invoke(this, EventArgs.Empty); return true; } protected override void OnInstanceDestroy(ulong instance) { if (Instance == 0) { // Unity might call destroy when instance handle was not successfully created // Ignore such cases since there's no resources associated with instance of 0. return; } if (SystemId != 0) { // Unity's OnSystemChange event won't trigger when destroying instance. // Reset resources associated with system before destroying the instance. SystemId = 0; if (OpenXRFeaturePluginManager.IsResponsibleForNativeLib(this)) { NativeLib.SetXrSystemId(0); } } InstanceDestroying?.Invoke(this, EventArgs.Empty); Instance = 0; if (OpenXRFeaturePluginManager.IsResponsibleForNativeLib(this)) { NativeLib.OnInstanceDestroyed(); } } protected override void OnSystemChange(ulong systemId) { SystemId = systemId; if (OpenXRFeaturePluginManager.IsResponsibleForNativeLib(this)) { NativeLib.SetXrSystemId(systemId); } } protected override void OnSessionCreate(ulong session) { Session = session; PluginEnvironmentSubsystem.OnSessionCreated(); if (OpenXRFeaturePluginManager.IsResponsibleForNativeLib(this)) { NativeLib.SetXrSession(session); } SessionCreated?.Invoke(this, EventArgs.Empty); } protected override void OnSessionBegin(ulong session) { // This virtual function is called right after xrSessionBegin returns, // All C# scripts should observe that the session is running. IsSessionRunning = true; if (OpenXRFeaturePluginManager.IsResponsibleForNativeLib(this)) { NativeLib.SetXrSessionRunning(true); } SessionBegun?.Invoke(this, EventArgs.Empty); } protected override void OnSessionStateChange(int oldState, int newState) { SessionState = (XrSessionState)newState; if (OpenXRFeaturePluginManager.IsResponsibleForNativeLib(this)) { NativeLib.SetSessionState((uint)newState); } } protected override void OnSessionEnd(ulong session) { SessionEnding?.Invoke(this, EventArgs.Empty); if (OpenXRFeaturePluginManager.IsResponsibleForNativeLib(this)) { NativeLib.SetXrSessionRunning(false); } // This virtual function is called right before xrSessionEnd is called. // All C# scripts should still observe that the session is running until this point. IsSessionRunning = false; } protected override void OnSessionDestroy(ulong session) { SessionDestroying?.Invoke(this, EventArgs.Empty); Session = 0; if (OpenXRFeaturePluginManager.IsResponsibleForNativeLib(this)) { NativeLib.SetXrSession(0); } } protected override void OnAppSpaceChange(ulong sceneOriginSpace) { SceneOriginSpace = sceneOriginSpace; if (OpenXRFeaturePluginManager.IsResponsibleForNativeLib(this)) { NativeLib.SetSceneOriginSpace(sceneOriginSpace); } } // Convert protected function to internal internal static new void SetEnvironmentBlendMode(XrEnvironmentBlendMode environmentBlendMode) { OpenXRFeature.SetEnvironmentBlendMode(environmentBlendMode); } // Convert protected function to internal internal static new XrEnvironmentBlendMode GetEnvironmentBlendMode() { return OpenXRFeature.GetEnvironmentBlendMode(); } void ISubsystemPlugin.CreateSubsystem(List descriptors, string id) => base.CreateSubsystem(descriptors, id); void ISubsystemPlugin.StartSubsystem() => base.StartSubsystem(); void ISubsystemPlugin.StopSubsystem() => base.StopSubsystem(); void ISubsystemPlugin.DestroySubsystem() => base.DestroySubsystem(); } }