//========= Copyright 2016-2023, HTC Corporation. All rights reserved. =========== using HTC.UnityPlugin.Utility; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEngine; #if VIU_STEAMVR_2_0_0_OR_NEWER && UNITY_STANDALONE using Valve.VR; #endif namespace HTC.UnityPlugin.VRModuleManagement { public partial class VRModule : SingletonBehaviour { private static DeviceState s_defaultState; private static SimulatorVRModule s_simulator; private static Dictionary s_deviceSerialNumberTable; [SerializeField] private bool m_dontDestroyOnLoad = true; [SerializeField] private bool m_lockPhysicsUpdateRateToRenderFrequency = true; [SerializeField] private VRModuleSelectEnum m_selectModule = VRModuleSelectEnum.Auto; [SerializeField] private VRModuleTrackingSpaceType m_trackingSpaceType = VRModuleTrackingSpaceType.RoomScale; [SerializeField] private NewPosesEvent m_onNewPoses = new NewPosesEvent(); [SerializeField] private NewInputEvent m_onNewInput = new NewInputEvent(); [SerializeField] private ControllerRoleChangedEvent m_onControllerRoleChanged = new ControllerRoleChangedEvent(); [SerializeField] private InputFocusEvent m_onInputFocus = new InputFocusEvent(); [SerializeField] private DeviceConnectedEvent m_onDeviceConnected = new DeviceConnectedEvent(); [SerializeField] private ActiveModuleChangedEvent m_onActiveModuleChanged = new ActiveModuleChangedEvent(); private bool m_delayDeactivate = false; private bool m_isDestoryed = false; private ModuleBase[] m_modules; private ModuleBase[] m_modulesOrdered; private VRModuleActiveEnum m_activatedModule = VRModuleActiveEnum.Uninitialized; private ModuleBase m_activatedModuleBase; private DeviceState[] m_prevStates; private DeviceState[] m_currStates; [RuntimeInitializeOnLoadMethod] private static void TryInitializeOnStartup() { if (VRModuleSettings.initializeOnStartup) { Initialize(); } } private static GameObject GetDefaultInitGameObject() { return new GameObject("[ViveInputUtility]"); } public static GameObject GetInstanceGameObject() { return Instance.gameObject; } protected override void OnSingletonBehaviourInitialized() { if (m_dontDestroyOnLoad && transform.parent == null && Application.isPlaying) { DontDestroyOnLoad(gameObject); } SetDefaultInitGameObjectGetter(GetDefaultInitGameObject); s_defaultState = new DeviceState(INVALID_DEVICE_INDEX); s_simulator = new SimulatorVRModule(); s_deviceSerialNumberTable = new Dictionary(16); m_activatedModule = VRModuleActiveEnum.Uninitialized; m_activatedModuleBase = null; try { var modules = new List(); var modulesOrdered = new List(); foreach (var type in Assembly.GetAssembly(typeof(ModuleBase)).GetTypes().Where(t => t.IsClass && !t.IsAbstract && t.IsSubclassOf(typeof(ModuleBase)))) { var inst = type == typeof(SimulatorVRModule) ? s_simulator : (ModuleBase)Activator.CreateInstance(type); var index = inst.moduleIndex; if (index < 0) { Debug.LogWarning("Invalid module index, module will not be activated! module name=" + type.Name + " index=" + index); } else if (index < modules.Count && modules[index] != null) { Debug.LogWarning("Duplicated module index, module will not be activated! module name=" + type.Name + " index=" + index); } else { while (index >= modules.Count) { modules.Add(null); } modules[index] = inst; } var order = inst.moduleOrder; if (order < 0) { Debug.LogWarning("Invalid module order, module will not be activated! module name=" + type.Name + " order=" + order); } else if (order < modulesOrdered.Count && modulesOrdered[order] != null) { Debug.LogWarning("Duplicated module order, module will not be activated! module name=" + type.Name + " order=" + order); } else { while (order >= modulesOrdered.Count) { modulesOrdered.Add(null); } modulesOrdered[order] = inst; } } m_modules = modules.ToArray(); m_modulesOrdered = modulesOrdered.ToArray(); } catch (Exception e) { m_modules = new ModuleBase[] { new DefaultModule() }; m_modulesOrdered = new ModuleBase[] { new DefaultModule() }; Debug.LogError(e); } } private uint GetDeviceStateLength() { return m_currStates == null ? 0u : (uint)m_currStates.Length; } private void EnsureDeviceStateLength(uint capacity) { // NOTE: this will clear out the array var cap = (int)(capacity < MAX_DEVICE_COUNT ? capacity : MAX_DEVICE_COUNT); if (GetDeviceStateLength() < cap) { if (m_currStates == null) { m_currStates = new DeviceState[cap]; } else { Array.Resize(ref m_currStates, cap); } if (m_prevStates == null) { m_prevStates = new DeviceState[cap]; } else { Array.Resize(ref m_prevStates, cap); } } } private bool TryGetValidDeviceState(uint index, out IVRModuleDeviceState prevState, out IVRModuleDeviceStateRW currState) { DeviceState prevRawState; DeviceState currRawState; if (TryGetValidDeviceState(index, out prevRawState, out currRawState)) { prevState = prevRawState; currState = currRawState; return true; } else { prevState = null; currState = null; return false; } } private bool TryGetValidDeviceState(uint index, out DeviceState prevState, out DeviceState currState) { if (m_currStates == null || index >= m_currStates.Length || m_currStates[index] == null) { prevState = null; currState = null; return false; } else { prevState = m_prevStates[index]; currState = m_currStates[index]; return true; } } private void EnsureValidDeviceState(uint index, out IVRModuleDeviceState prevState, out IVRModuleDeviceStateRW currState) { EnsureDeviceStateLength(index + 1); if (!TryGetValidDeviceState(index, out prevState, out currState)) { prevState = m_prevStates[index] = new DeviceState(index); currState = m_currStates[index] = new DeviceState(index); } } // this function will skip VRModule.HMD_DEVICE_INDEX (preserved index for HMD) private uint FindAndEnsureUnusedNotHMDDeviceState(out IVRModuleDeviceState prevState, out IVRModuleDeviceStateRW currState) { var index = (m_activatedModuleBase == null ? VRModule.HMD_DEVICE_INDEX : m_activatedModuleBase.reservedDeviceIndex) + 1u; var len = GetDeviceStateLength(); for (; index < len; ++index) { if (TryGetValidDeviceState(index, out prevState, out currState)) { if (prevState.isConnected) { continue; } if (currState.isConnected) { continue; } return index; } break; } EnsureValidDeviceState(index, out prevState, out currState); return index; } private void Update() { if (!IsInstance) { return; } // Get should activate module var shouldActivateModule = GetShouldActivateModule(); // Update module activity if (m_activatedModule != shouldActivateModule) { // Do clean up if (m_activatedModule != VRModuleActiveEnum.Uninitialized) { DeactivateModule(); } if (shouldActivateModule != VRModuleActiveEnum.Uninitialized) { ActivateModule(shouldActivateModule); } } if (m_activatedModuleBase != null) { m_activatedModuleBase.Update(); } } private void FixedUpdate() { if (!IsInstance) { return; } if (m_activatedModuleBase != null) { m_activatedModuleBase.FixedUpdate(); } } private void LateUpdate() { if (!IsInstance) { return; } if (m_activatedModuleBase != null) { m_activatedModuleBase.LateUpdate(); } } protected override void OnDestroy() { if (IsInstance) { m_isDestoryed = true; if (!m_delayDeactivate) { DeactivateModule(); } } base.OnDestroy(); } private VRModuleActiveEnum GetShouldActivateModule() { if (IsApplicationQuitting || m_isDestoryed) { return VRModuleActiveEnum.Uninitialized; } if (m_selectModule == VRModuleSelectEnum.Auto) { for (int i = m_modulesOrdered.Length - 1; i >= 0; --i) { if (m_modulesOrdered[i] != null && m_modulesOrdered[i].ShouldActiveModule()) { return (VRModuleActiveEnum)m_modulesOrdered[i].moduleIndex; } } } else if (m_selectModule >= 0 && (int)m_selectModule < m_modules.Length) { return (VRModuleActiveEnum)m_selectModule; } else { return VRModuleActiveEnum.None; } return VRModuleActiveEnum.Uninitialized; } private void ActivateModule(VRModuleActiveEnum module) { if (m_activatedModule != VRModuleActiveEnum.Uninitialized) { Debug.LogError("Must deactivate before activate module! Current activatedModule:" + m_activatedModule); return; } if (module == VRModuleActiveEnum.Uninitialized) { Debug.LogError("Activate module cannot be Uninitialized! Use DeactivateModule instead"); return; } m_activatedModule = module; m_activatedModuleBase = m_modules[(int)module]; m_activatedModuleBase.Activated(); #if UNITY_2017_1_OR_NEWER Application.onBeforeRender += BeforeRenderUpdateModule; #else Camera.onPreCull += OnCameraPreCull; #endif InvokeActiveModuleChangedEvent(m_activatedModule); } #if !UNITY_2017_1_OR_NEWER private int m_preCullOnceFrame = -1; private void OnCameraPreCull(Camera cam) { var thisFrame = Time.frameCount; if (m_preCullOnceFrame == thisFrame) { return; } #if UNITY_5_5_OR_NEWER if ((cam.cameraType & (CameraType.Game | CameraType.VR)) == 0) { return; } #else if ((cam.cameraType & CameraType.Game) == 0) { return; } #endif m_preCullOnceFrame = thisFrame; BeforeRenderUpdateModule(); } #endif private void BeforeRenderUpdateModule() { if (m_activatedModuleBase != null) { m_activatedModuleBase.BeforeRenderUpdate(); } } private void DeactivateModule() { if (m_activatedModule == VRModuleActiveEnum.Uninitialized) { return; } if (m_activatedModuleBase == null) { return; } m_delayDeactivate = false; #if UNITY_2017_1_OR_NEWER Application.onBeforeRender -= BeforeRenderUpdateModule; #else Camera.onPreCull -= OnCameraPreCull; #endif DeviceState prevState; DeviceState currState; // copy status to from current state to previous state, and reset current state for (uint i = 0u, imax = GetDeviceStateLength(); i < imax; ++i) { if (!TryGetValidDeviceState(i, out prevState, out currState)) { continue; } if (prevState.isConnected || currState.isConnected) { prevState.CopyFrom(currState); currState.Reset(); } } s_deviceSerialNumberTable.Clear(); // send disconnect event SendAllDeviceConnectedEvent(); var deactivatedModuleBase = m_activatedModuleBase; m_activatedModule = VRModuleActiveEnum.Uninitialized; m_activatedModuleBase = null; deactivatedModuleBase.Deactivated(); InvokeActiveModuleChangedEvent(VRModuleActiveEnum.Uninitialized); } private void ModuleFlushDeviceState() { DeviceState prevState; DeviceState currState; // copy status to from current state to previous state for (uint i = 0u, imax = GetDeviceStateLength(); i < imax; ++i) { if (!TryGetValidDeviceState(i, out prevState, out currState)) { continue; } if (prevState.isConnected || currState.isConnected) { prevState.CopyFrom(currState); } } } private void ModuleConnectedDeviceChanged() { DeviceState prevState; DeviceState currState; m_delayDeactivate = true; List connected = null; List disconnected = null; // send connect/disconnect event for (uint i = 0u, imax = GetDeviceStateLength(); i < imax; ++i) { if (!TryGetValidDeviceState(i, out prevState, out currState)) { continue; } if (!prevState.isConnected) { if (currState.isConnected) { if (connected == null) { connected = ListPool.Get(); } connected.Add(i); } } else { if (!currState.isConnected) { if (disconnected == null) { disconnected = ListPool.Get(); } disconnected.Add(i); } } } if (disconnected != null) { for (int i = 0, imax = disconnected.Count; i < imax; ++i) { var index = disconnected[i]; var state = m_prevStates[index]; s_deviceSerialNumberTable.Remove(state.serialNumber); } ListPool.Release(disconnected); } if (connected != null) { for (int i = 0, imax = connected.Count; i < imax; ++i) { var index = connected[i]; var state = m_currStates[index]; if (string.IsNullOrEmpty(state.serialNumber)) { Debug.LogError("Device connected with empty serialNumber. index:" + state.deviceIndex); } else if (s_deviceSerialNumberTable.ContainsKey(state.serialNumber)) { Debug.LogError("Device connected with duplicate serialNumber: " + state.serialNumber + " index:" + state.deviceIndex + "(" + s_deviceSerialNumberTable[state.serialNumber] + ")"); } else { s_deviceSerialNumberTable.Add(state.serialNumber, index); } } ListPool.Release(connected); } SendAllDeviceConnectedEvent(); m_delayDeactivate = false; if (m_isDestoryed) { DeactivateModule(); } } private void SendAllDeviceConnectedEvent() { DeviceState prevState; DeviceState currState; for (uint i = 0u, imax = GetDeviceStateLength(); i < imax; ++i) { if (!TryGetValidDeviceState(i, out prevState, out currState)) { continue; } if (prevState.isConnected != currState.isConnected) { InvokeDeviceConnectedEvent(i, currState.isConnected); } } } } }