// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Microsoft.MixedReality.Toolkit.Input; using Microsoft.MixedReality.Toolkit.Utilities; using System; using System.Collections.Generic; using Unity.Profiling; using UnityEngine; using UnityEngine.XR; #if XR_MANAGEMENT_ENABLED using UnityEngine.XR.Management; #endif // XR_MANAGEMENT_ENABLED namespace Microsoft.MixedReality.Toolkit.XRSDK.Input { /// /// Manages XR SDK devices. /// [MixedRealityDataProvider( typeof(IMixedRealityInputSystem), SupportedPlatforms.WindowsStandalone | SupportedPlatforms.MacStandalone | SupportedPlatforms.LinuxStandalone | SupportedPlatforms.WindowsUniversal, "XR SDK Device Manager", supportedUnityXRPipelines: SupportedUnityXRPipelines.XRSDK)] public class XRSDKDeviceManager : BaseInputDeviceManager, IMixedRealityCapabilityCheck { /// /// Constructor. /// /// The instance that receives data from this provider. /// Friendly name of the service. /// Service priority. Used to determine order of instantiation. /// The service's configuration profile. public XRSDKDeviceManager( IMixedRealityInputSystem inputSystem, string name = null, uint priority = DefaultPriority, BaseMixedRealityProfile profile = null) : base(inputSystem, name, priority, profile) { } /// public virtual bool CheckCapability(MixedRealityCapability capability) { // The XR SDK platform supports motion controllers. return (capability == MixedRealityCapability.MotionController); } protected readonly Dictionary ActiveControllers = new Dictionary(); private readonly List inputDevices = new List(); private readonly List inputDevicesSubset = new List(); private readonly List lastInputDevices = new List(); protected virtual List DesiredInputCharacteristics { get; set; } = new List() { InputDeviceCharacteristics.Controller, InputDeviceCharacteristics.HandTracking }; #if XR_MANAGEMENT_ENABLED /// /// Checks if the active loader has a specific name. Used in cases where the loader class is internal, like WindowsMRLoader. /// /// The string name to compare against the active loader. /// True if the active loader has the same name as the parameter. [Obsolete("Use XRSDKLoaderHelpers instead.")] protected virtual bool IsLoaderActive(string loaderName) => LoaderHelpers.IsLoaderActive(loaderName) ?? false; /// /// Checks if the active loader is of a specific type. Used in cases where the loader class is accessible, like OculusLoader. /// /// The loader class type to check against the active loader. /// True if the active loader is of the specified type. [Obsolete("Use XRSDKLoaderHelpers instead.")] protected virtual bool IsLoaderActive() where T : XRLoader => LoaderHelpers.IsLoaderActive() ?? false; #endif // XR_MANAGEMENT_ENABLED private static readonly ProfilerMarker UpdatePerfMarker = new ProfilerMarker("[MRTK] XRSDKDeviceManager.Update"); public override void Enable() { base.Enable(); inputDevices.Clear(); foreach (InputDeviceCharacteristics inputDeviceCharacteristics in DesiredInputCharacteristics) { InputDevices.GetDevicesWithCharacteristics(inputDeviceCharacteristics, inputDevicesSubset); foreach (InputDevice device in inputDevicesSubset) { if (!inputDevices.Contains(device)) { inputDevices.Add(device); } } } InputDevices.deviceConnected += InputDevices_deviceConnected; InputDevices.deviceDisconnected += InputDevices_deviceDisconnected; } /// public override void Update() { using (UpdatePerfMarker.Auto()) { if (!IsEnabled) { return; } base.Update(); if (XRSubsystemHelpers.InputSubsystem == null || !XRSubsystemHelpers.InputSubsystem.running) { return; } foreach (InputDevice device in inputDevices) { if (device.isValid) { GenericXRSDKController controller = GetOrAddController(device); if (controller != null && lastInputDevices.Contains(device)) { // Remove devices from our previously tracked list as we update them. // This will allow us to remove all stale devices that were tracked // last frame but not this one. lastInputDevices.Remove(device); controller.UpdateController(device); } } } foreach (InputDevice device in lastInputDevices) { RemoveController(device); } lastInputDevices.Clear(); lastInputDevices.AddRange(inputDevices); } } /// public override void Disable() { InputDevices.deviceConnected -= InputDevices_deviceConnected; InputDevices.deviceDisconnected -= InputDevices_deviceDisconnected; var controllersCopy = ActiveControllers.ToReadOnlyCollection(); foreach (KeyValuePair controller in controllersCopy) { RemoveController(controller.Key); } base.Disable(); } private void InputDevices_deviceConnected(InputDevice obj) { bool characteristicsMatch = (obj.characteristics & (InputDeviceCharacteristics.Controller | InputDeviceCharacteristics.HandTracking)) > 0; if (characteristicsMatch && !inputDevices.Contains(obj)) { inputDevices.Add(obj); } } private void InputDevices_deviceDisconnected(InputDevice obj) { inputDevices.Remove(obj); } #region Controller Utilities private static readonly ProfilerMarker GetOrAddControllerPerfMarker = new ProfilerMarker("[MRTK] XRSDKDeviceManager.GetOrAddController"); /// /// Gets or adds a controller using the InputDevice name provided. /// /// The InputDevice from XR SDK. /// The controller reference. protected virtual GenericXRSDKController GetOrAddController(InputDevice inputDevice) { using (GetOrAddControllerPerfMarker.Auto()) { // If a device is already registered with the ID provided, just return it. if (ActiveControllers.ContainsKey(inputDevice)) { var controller = ActiveControllers[inputDevice]; Debug.Assert(controller != null); return controller; } Handedness controllingHand; if (inputDevice.characteristics.IsMaskSet(InputDeviceCharacteristics.Left)) { controllingHand = Handedness.Left; } else if (inputDevice.characteristics.IsMaskSet(InputDeviceCharacteristics.Right)) { controllingHand = Handedness.Right; } else { controllingHand = Handedness.None; } SupportedControllerType currentControllerType = GetCurrentControllerType(inputDevice); Type controllerType = GetControllerType(currentControllerType); if (controllerType == null) { return null; } InputSourceType inputSourceType = GetInputSourceType(currentControllerType); IMixedRealityPointer[] pointers = RequestPointers(currentControllerType, controllingHand); IMixedRealityInputSource inputSource = Service?.RequestNewGenericInputSource($"{currentControllerType} Controller {controllingHand}", pointers, inputSourceType); GenericXRSDKController detectedController = Activator.CreateInstance(controllerType, TrackingState.NotTracked, controllingHand, inputSource, null) as GenericXRSDKController; if (detectedController == null || !detectedController.Enabled) { // Controller failed to be set up correctly. Debug.LogError($"Failed to create {controllerType.Name} controller"); // Return null so we don't raise the source detected. return null; } for (int i = 0; i < detectedController.InputSource?.Pointers?.Length; i++) { detectedController.InputSource.Pointers[i].Controller = detectedController; } ActiveControllers.Add(inputDevice, detectedController); Service?.RaiseSourceDetected(detectedController.InputSource, detectedController); return detectedController; } } private static readonly ProfilerMarker RemoveControllerPerfMarker = new ProfilerMarker("[MRTK] XRSDKDeviceManager.RemoveController"); /// /// Gets the current controller type for the InputDevice name provided. /// /// The InputDevice from XR SDK. protected virtual void RemoveController(InputDevice inputDevice) { using (RemoveControllerPerfMarker.Auto()) { if (ActiveControllers.TryGetValue(inputDevice, out GenericXRSDKController controller)) { if (controller != null) { RemoveControllerFromScene(controller); } ActiveControllers.Remove(inputDevice); } } } /// /// Removes the controller from the scene and handles any additional cleanup /// protected void RemoveControllerFromScene(GenericXRSDKController controller) { Service?.RaiseSourceLost(controller.InputSource, controller); RecyclePointers(controller.InputSource); var visualizer = controller.Visualizer; if (!visualizer.IsNull() && visualizer.GameObjectProxy != null) { visualizer.GameObjectProxy.SetActive(false); } } /// /// Gets the concrete type of the detected controller, based on the and defined per-platform. /// /// The current controller type. /// The concrete type of the currently detected controller. protected virtual Type GetControllerType(SupportedControllerType supportedControllerType) { return typeof(GenericXRSDKController); } /// /// Returns the of the currently detected controller, based on the . /// /// The current controller type. /// The enum value of the currently detected controller's InputSource type. protected virtual InputSourceType GetInputSourceType(SupportedControllerType supportedControllerType) { return InputSourceType.Controller; } /// /// Gets the current controller type for the InputDevice name provided. /// /// The InputDevice from XR SDK. /// The supported controller type. protected virtual SupportedControllerType GetCurrentControllerType(InputDevice inputDevice) { Debug.Log($"{inputDevice.name} does not have a defined controller type, falling back to generic controller type"); return SupportedControllerType.GenericUnity; } #endregion Controller Utilities } }