//========= Copyright 2016-2023, HTC Corporation. All rights reserved. =========== using HTC.UnityPlugin.Utility; using HTC.UnityPlugin.VRModuleManagement; using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; namespace HTC.UnityPlugin.Vive { public static partial class ViveRole { public struct MappingChangedEventArg { public int roleValue; public uint previousDeviceIndex; public uint currentDeviceIndex; } public struct MappingChangedEventArg { public TRole role; public uint previousDeviceIndex; public uint currentDeviceIndex; } public interface IMap { ViveRoleEnum.IInfo RoleValueInfo { get; } IMapHandler Handler { get; } int BindingCount { get; } IIndexedTableReadOnly BindingTable { get; } bool IsRoleValueMapped(int roleValue); bool IsDeviceMapped(uint deviceIndex); uint GetMappedDeviceByRoleValue(int roleValue); int GetMappedRoleValueByDevice(uint deviceIndex); [Obsolete("Use BindDeviceToRoleValue instead")] void BindRoleValue(int roleValue, string deviceSN); void BindDeviceToRoleValue(string deviceSN, int roleValue); void BindAll(); bool UnbindRoleValue(int roleValue); // return true if role is ready for bind bool UnbindDevice(string deviceSN); // return true if device is ready for bind bool UnbindConnectedDevice(uint deviceIndex); // return true if device is ready for bind void UnbindAll(); bool IsRoleValueBound(int roleValue); bool IsDeviceBound(string deviceSN); bool IsDeviceConnectedAndBound(uint deviceIndex); string GetBoundDeviceByRoleValue(int roleValue); /// /// Should use IsDeviceBound to validate deviceSN before calling this function /// int GetBoundRoleValueByDevice(string deviceSN); /// /// Should use IsDeviceConnectedAndBound to validate deviceIndex before calling this function /// int GetBoundRoleValueByConnectedDevice(uint deviceIndex); event UnityAction onRoleValueMappingChanged; } private sealed class Map : IMap { private readonly ViveRoleEnum.IInfo m_info; private IMapHandler m_handler; private bool m_lockInternalMapping; // mapping table private readonly uint[] m_role2index; private readonly int[] m_index2role; // binding table private readonly IndexedSet[] m_roleBoundDevices; // connected devices only private readonly IndexedTable m_sn2role; public Map(Type roleType) { m_info = ViveRoleEnum.GetInfo(roleType); m_role2index = new uint[m_info.ValidRoleLength]; m_index2role = new int[VRModule.MAX_DEVICE_COUNT]; m_roleBoundDevices = new IndexedSet[m_info.ValidRoleLength]; m_sn2role = new IndexedTable(Mathf.Min(m_info.ValidRoleLength, (int)VRModule.MAX_DEVICE_COUNT)); for (int i = 0; i < m_role2index.Length; ++i) { m_role2index[i] = VRModule.INVALID_DEVICE_INDEX; } for (int i = 0; i < m_index2role.Length; ++i) { m_index2role[i] = m_info.InvalidRoleValue; } } public ViveRoleEnum.IInfo RoleValueInfo { get { return m_info; } } public int BindingCount { get { return m_sn2role.Count; } } public IIndexedTableReadOnly BindingTable { get { return m_sn2role.ReadOnly; } } public IMapHandler Handler { get { return m_handler; } set { if (m_handler == value) { return; } if (m_handler != null) { m_handler.OnDivestedOfCurrentMapHandler(); m_handler = null; } if (value != null) { if (value.BlockBindings) { UnbindAll(); } m_handler = value; m_handler.OnAssignedAsCurrentMapHandler(); } } } public event UnityAction onRoleValueMappingChanged; private string DeviceSN(uint deviceIndex) { return VRModule.GetCurrentDeviceState(deviceIndex).serialNumber; } public void OnConnectedDeviceChanged(uint deviceIndex, VRModuleDeviceClass deviceClass, string deviceSN, bool connected) { if (connected) { if (IsDeviceBound(deviceSN)) { InternalInsertRoleBoundDevice(deviceSN, deviceIndex, GetBoundRoleValueByDevice(deviceSN)); } } else { if (IsDeviceMapped(deviceIndex)) { if (IsDeviceBound(deviceSN)) { InternalRemoveRoleBoundDevice(deviceSN, deviceIndex, GetBoundRoleValueByDevice(deviceSN)); } if (IsDeviceMapped(deviceIndex)) { InternalUnmapping(GetMappedRoleValueByDevice(deviceIndex), deviceIndex); } } } if (m_handler != null) { m_handler.OnConnectedDeviceChanged(deviceIndex, deviceClass, deviceSN, connected); } } public void OnTrackedDeviceRoleChanged() { if (m_handler != null) { m_handler.OnTrackedDeviceRoleChanged(); } } #region retrieve state public bool IsRoleValueMapped(int roleValue) { if (!m_info.IsValidRoleValue(roleValue)) { return false; } return IsRoleOffsetMapped(m_info.RoleValueToRoleOffset(roleValue)); } public bool IsRoleOffsetMapped(int roleOffset) { return VRModule.IsValidDeviceIndex(m_role2index[roleOffset]); } public bool IsDeviceMapped(uint deviceIndex) { return VRModule.IsValidDeviceIndex(deviceIndex) && m_info.IsValidRoleValue(m_index2role[deviceIndex]); } public bool IsRoleValueBound(int roleValue) { if (!m_info.IsValidRoleValue(roleValue)) { return false; } var roleOffset = m_info.RoleValueToRoleOffset(roleValue); return m_roleBoundDevices[roleOffset] != null && m_roleBoundDevices[roleOffset].Count > 0; } public bool IsDeviceBound(string deviceSN) { return string.IsNullOrEmpty(deviceSN) ? false : m_sn2role.ContainsKey(deviceSN); } public bool IsDeviceConnectedAndBound(uint deviceIndex) { return IsDeviceBound(DeviceSN(deviceIndex)); } public uint GetMappedDeviceByRoleValue(int roleValue) { if (m_info.IsValidRoleValue(roleValue)) { return m_role2index[m_info.RoleValueToRoleOffset(roleValue)]; } else { return VRModule.INVALID_DEVICE_INDEX; } } public int GetMappedRoleValueByDevice(uint deviceIndex) { if (VRModule.IsValidDeviceIndex(deviceIndex)) { return m_index2role[deviceIndex]; } else { return m_info.InvalidRoleValue; } } public string GetBoundDeviceByRoleValue(int roleValue) { if (!IsRoleValueBound(roleValue)) { return string.Empty; } return DeviceSN(GetMappedDeviceByRoleValue(roleValue)); } public int GetBoundRoleValueByDevice(string deviceSN) { return m_sn2role[deviceSN]; } public int GetBoundRoleValueByConnectedDevice(uint deviceIndex) { return GetBoundRoleValueByDevice(DeviceSN(deviceIndex)); } #endregion retrieve state #region internal operation // both roleValue and deviceIndex must be valid // ignore binding state private void InternalMapping(int roleValue, uint deviceIndex) { if (m_lockInternalMapping) { throw new Exception("Recursive calling InternalMapping"); } m_lockInternalMapping = true; var previousRoleValue = m_index2role[deviceIndex]; if (roleValue == previousRoleValue) { m_lockInternalMapping = false; return; } if (m_info.IsValidRoleValue(previousRoleValue)) { m_lockInternalMapping = false; InternalUnmapping(previousRoleValue, deviceIndex); m_lockInternalMapping = true; } var roleOffset = m_info.RoleValueToRoleOffset(roleValue); var previousDeviceIndex = m_role2index[roleOffset]; var eventArg = new MappingChangedEventArg() { roleValue = roleValue, previousDeviceIndex = previousDeviceIndex, currentDeviceIndex = deviceIndex, }; m_role2index[roleOffset] = deviceIndex; m_index2role[deviceIndex] = roleValue; if (VRModule.IsValidDeviceIndex(previousDeviceIndex)) { m_index2role[previousDeviceIndex] = m_info.InvalidRoleValue; } if (onRoleValueMappingChanged != null) { onRoleValueMappingChanged(this, eventArg); } m_lockInternalMapping = false; } // both roleValue and deviceIndex must be valid // ignore binding state private void InternalUnmapping(int roleValue, uint deviceIndex) { if (m_lockInternalMapping) { throw new Exception("Recursive calling InternalMapping"); } m_lockInternalMapping = true; var roleOffset = m_info.RoleValueToRoleOffset(roleValue); var eventArg = new MappingChangedEventArg() { roleValue = roleValue, previousDeviceIndex = deviceIndex, currentDeviceIndex = VRModule.INVALID_DEVICE_INDEX, }; m_role2index[roleOffset] = VRModule.INVALID_DEVICE_INDEX; m_index2role[deviceIndex] = m_info.InvalidRoleValue; if (onRoleValueMappingChanged != null) { onRoleValueMappingChanged(this, eventArg); } m_lockInternalMapping = false; } // device must be valid and connected and have bound role value // device must not exist in role bound devices // boundRoleValue can be whether valid or not private void InternalInsertRoleBoundDevice(string deviceSN, uint deviceIndex, int boundRoleValue) { if (m_info.IsValidRoleValue(boundRoleValue)) { var roleBoundDevices = InternalGetRoleBoundDevices(boundRoleValue); roleBoundDevices.Add(deviceIndex); // if key already added here, means that this device already in role bound devices InternalMapping(boundRoleValue, deviceIndex); } } // device must be valid and connected and have bound role value // device must already exist in role bound devices // boundRoleValue can be whether valid or not private void InternalRemoveRoleBoundDevice(string deviceSN, uint deviceIndex, int boundRoleValue) { if (m_info.IsValidRoleValue(boundRoleValue)) { var roleBoundDevices = InternalGetRoleBoundDevices(boundRoleValue); if (!roleBoundDevices.Remove(deviceIndex)) { throw new Exception("device([" + deviceIndex + "]" + deviceSN + ") has not been InternalMappingRoleBoundDevice"); } if (roleBoundDevices.Count > 0) { InternalMapping(boundRoleValue, roleBoundDevices[0]); } } } // deviceSN must be valid // device can be whether bound or not // device can be whether connected or not private void InternalBind(string deviceSN, int roleValue) { var deviceIndex = VRModule.GetConnectedDeviceIndex(deviceSN); bool previousIsBound = false; int previousBoundRoleValue = m_info.InvalidRoleValue; if (m_sn2role.TryGetValue(deviceSN, out previousBoundRoleValue)) { if (previousBoundRoleValue == roleValue) { return; } previousIsBound = true; m_sn2role.Remove(deviceSN); if (VRModule.IsValidDeviceIndex(deviceIndex)) { InternalRemoveRoleBoundDevice(deviceSN, deviceIndex, previousBoundRoleValue); } } m_sn2role[deviceSN] = roleValue; if (VRModule.IsValidDeviceIndex(deviceIndex)) { InternalInsertRoleBoundDevice(deviceSN, deviceIndex, roleValue); } if (m_handler != null) { m_handler.OnBindingRoleValueChanged(deviceSN, previousIsBound, previousBoundRoleValue, true, roleValue); } } // deviceSN must be valid // device must be bound // device can be whether connected or not private void InternalUnbind(string deviceSN, int boundRoleValue) { var deviceIndex = VRModule.GetConnectedDeviceIndex(deviceSN); if (!m_sn2role.Remove(deviceSN)) { throw new Exception("device([" + deviceIndex + "]" + deviceSN + ") already unbound"); } if (VRModule.IsValidDeviceIndex(deviceIndex)) { InternalRemoveRoleBoundDevice(deviceSN, deviceIndex, boundRoleValue); } if (m_handler != null) { m_handler.OnBindingRoleValueChanged(deviceSN, true, boundRoleValue, false, m_info.InvalidRoleValue); } } #endregion internal operation #region mapping public void MappingRoleValue(int roleValue, uint deviceIndex) { if (!m_info.IsValidRoleValue(roleValue)) { throw new ArgumentException("Cannot mapping invalid roleValue(" + m_info.RoleEnumType.Name + "[" + roleValue + "])"); } if (!VRModule.IsValidDeviceIndex(deviceIndex)) { throw new ArgumentException("Cannot mapping invalid deviceIndex(" + deviceIndex + ")"); } if (IsRoleValueBound(roleValue)) { throw new ArgumentException("roleValue(" + m_info.RoleEnumType.Name + "[" + roleValue + "]) is already bound, unbind first."); } if (IsDeviceConnectedAndBound(deviceIndex)) { throw new ArgumentException("deviceIndex(" + deviceIndex + ") is already bound, unbind first"); } InternalMapping(roleValue, deviceIndex); } // return true if role is ready for mapping public bool UnmappingRoleValue(int roleValue) { // is mapped? if (!IsRoleValueMapped(roleValue)) { return false; } // is bound? if (IsRoleValueBound(roleValue)) { return false; } InternalUnmapping(roleValue, GetMappedDeviceByRoleValue(roleValue)); return true; } // return true if device is ready for mapping public bool UnmappingDevice(uint deviceIndex) { // is mapped? if (!IsDeviceMapped(deviceIndex)) { return false; } // is bound? if (IsDeviceConnectedAndBound(deviceIndex)) { return false; } InternalUnmapping(GetMappedRoleValueByDevice(deviceIndex), deviceIndex); return true; } public void UnmappingAll() { for (int roleValue = m_info.MinValidRoleValue; roleValue <= m_info.MaxValidRoleValue; ++roleValue) { if (!m_info.IsValidRoleValue(roleValue)) { continue; } UnmappingRoleValue(roleValue); } } #endregion mapping #region bind [Obsolete("Use BindDeviceToRoleValue instead")] public void BindRoleValue(int roleValue, string deviceSN) { BindDeviceToRoleValue(deviceSN, roleValue); } public void BindDeviceToRoleValue(string deviceSN, int roleValue) { if (string.IsNullOrEmpty(deviceSN)) { throw new ArgumentException("deviceSN cannot be null or empty."); } if (m_handler != null && m_handler.BlockBindings) { return; } InternalBind(deviceSN, roleValue); } // bind all mapped roles & devices public void BindAll() { if (m_handler != null && m_handler.BlockBindings) { return; } for (int roleValue = m_info.MinValidRoleValue; roleValue <= m_info.MaxValidRoleValue; ++roleValue) { if (!m_info.IsValidRoleValue(roleValue)) { continue; } if (IsRoleValueMapped(roleValue) && !IsRoleValueBound(roleValue)) { InternalBind(DeviceSN(GetMappedDeviceByRoleValue(roleValue)), roleValue); } } } public bool UnbindRoleValue(int roleValue) { if (!IsRoleValueBound(roleValue)) { return false; } var roleBoundDevices = InternalGetRoleBoundDevices(roleValue); var boundDeviceIndex = GetMappedDeviceByRoleValue(roleValue); // unbind other bound device first, to avoid redundent mapping changes event while (roleBoundDevices.Count > 1) { for (int i = roleBoundDevices.Count - 1; i >= 0; --i) { if (roleBoundDevices[i] != boundDeviceIndex) { InternalUnbind(DeviceSN(roleBoundDevices[i]), roleValue); break; } } }; if (roleBoundDevices.Count == 1) { InternalUnbind(DeviceSN(boundDeviceIndex), roleValue); } return true; } public bool UnbindDevice(string deviceSN) { if (!IsDeviceBound(deviceSN)) { return false; } InternalUnbind(deviceSN, GetBoundRoleValueByDevice(deviceSN)); return true; } public bool UnbindConnectedDevice(uint deviceIndex) { return UnbindDevice(DeviceSN(deviceIndex)); } public void UnbindAll() { for (int i = m_sn2role.Count - 1; i >= 0; --i) { UnbindDevice(m_sn2role.GetKeyByIndex(i)); } } // roleValue must be valid private IndexedSet InternalGetRoleBoundDevices(int roleValue) { var roleOffset = m_info.RoleValueToRoleOffset(roleValue); if (m_roleBoundDevices[roleOffset] == null) { m_roleBoundDevices[roleOffset] = new IndexedSet(); } return m_roleBoundDevices[roleOffset]; } #endregion bind } public interface IMap : IMap { ViveRoleEnum.IInfo RoleInfo { get; } bool IsRoleMapped(TRole role); uint GetMappedDeviceByRole(TRole role); TRole GetMappedRoleByDevice(uint deviceIndex); [Obsolete("Use BindDeviceToRole instead")] void BindRole(TRole role, string deviceSN); void BindDeviceToRole(string deviceSN, TRole role); bool UnbindRole(TRole role); // return true if role is ready for bind bool IsRoleBound(TRole role); string GetBoundDeviceByRole(TRole role); TRole GetBoundRoleByDevice(string deviceSN); TRole GetBoundRoleByConnectedDevice(uint deviceIndex); event UnityAction, MappingChangedEventArg> onRoleMappingChanged; } private sealed class GenericMap : IMap { public static GenericMap s_instance; private readonly ViveRoleEnum.IInfo m_info; private readonly Map m_map; public GenericMap() { m_info = ViveRoleEnum.GetInfo(); m_map = GetInternalMap(typeof(TRole)); if (s_instance == null) { s_instance = this; } else { Debug.LogWarning("duplicated instance for RoleInfo<" + typeof(TRole).Name + ">"); } m_map.onRoleValueMappingChanged += OnMappingChanged; } public ViveRoleEnum.IInfo RoleValueInfo { get { return m_map.RoleValueInfo; } } public ViveRoleEnum.IInfo RoleInfo { get { return m_info; } } public IMapHandler Handler { get { return m_map.Handler; } } public int BindingCount { get { return m_map.BindingCount; } } public IIndexedTableReadOnly BindingTable { get { return m_map.BindingTable; } } public event UnityAction onRoleValueMappingChanged { add { m_map.onRoleValueMappingChanged += value; } remove { m_map.onRoleValueMappingChanged -= value; } } public event UnityAction, MappingChangedEventArg> onRoleMappingChanged; private void OnMappingChanged(IMap map, MappingChangedEventArg arg) { if (onRoleMappingChanged != null) { onRoleMappingChanged(this, new MappingChangedEventArg() { role = m_info.ToRole(arg.roleValue), previousDeviceIndex = arg.previousDeviceIndex, currentDeviceIndex = arg.currentDeviceIndex, }); } } public void MappingRole(TRole role, uint deviceIndex) { m_map.MappingRoleValue(m_info.ToRoleValue(role), deviceIndex); } public void MappingRoleValue(int roleValue, uint deviceIndex) { m_map.MappingRoleValue(roleValue, deviceIndex); } public bool UnmappingRole(TRole role) { return m_map.UnmappingRoleValue(m_info.ToRoleValue(role)); } public bool UnmappingRoleValue(int roleValue) { return m_map.UnmappingRoleValue(roleValue); } public bool UnmappingDevice(uint deviceIndex) { return m_map.UnmappingDevice(deviceIndex); } public void UnmappingAll() { m_map.UnmappingAll(); } public bool IsRoleMapped(TRole role) { return m_map.IsRoleValueMapped(m_info.ToRoleValue(role)); } public bool IsRoleValueMapped(int roleValue) { return m_map.IsRoleValueMapped(roleValue); } public bool IsDeviceMapped(uint deviceIndex) { return m_map.IsDeviceMapped(deviceIndex); } public uint GetMappedDeviceByRole(TRole role) { return m_map.GetMappedDeviceByRoleValue(m_info.ToRoleValue(role)); } public TRole GetMappedRoleByDevice(uint deviceIndex) { return m_info.ToRole(m_map.GetMappedRoleValueByDevice(deviceIndex)); } public uint GetMappedDeviceByRoleValue(int roleValue) { return m_map.GetMappedDeviceByRoleValue(roleValue); } public int GetMappedRoleValueByDevice(uint deviceIndex) { return m_map.GetMappedRoleValueByDevice(deviceIndex); } [Obsolete("Use BindDeviceToRole instead")] public void BindRole(TRole role, string deviceSN) { m_map.BindDeviceToRoleValue(deviceSN, m_info.ToRoleValue(role)); } [Obsolete("Use BindDeviceToRoleValue instead")] public void BindRoleValue(int roleValue, string deviceSN) { m_map.BindDeviceToRoleValue(deviceSN, roleValue); } public void BindDeviceToRole(string deviceSN, TRole role) { m_map.BindDeviceToRoleValue(deviceSN, m_info.ToRoleValue(role)); } public void BindDeviceToRoleValue(string deviceSN, int roleValue) { m_map.BindDeviceToRoleValue(deviceSN, roleValue); } public void BindAll() { m_map.BindAll(); } public bool UnbindRole(TRole role) { return m_map.UnbindRoleValue(m_info.ToRoleValue(role)); } public bool UnbindRoleValue(int roleValue) { return m_map.UnbindRoleValue(roleValue); } public bool UnbindDevice(string deviceSN) { return m_map.UnbindDevice(deviceSN); } public bool UnbindConnectedDevice(uint deviceIndex) { return UnbindConnectedDevice(deviceIndex); } public void UnbindAll() { m_map.UnbindAll(); } public bool IsRoleBound(TRole role) { return m_map.IsRoleValueBound(m_info.ToRoleValue(role)); } public bool IsRoleValueBound(int roleValue) { return m_map.IsRoleValueBound(roleValue); } public bool IsDeviceBound(string deviceSN) { return m_map.IsDeviceBound(deviceSN); } public bool IsDeviceConnectedAndBound(uint deviceIndex) { return m_map.IsDeviceConnectedAndBound(deviceIndex); } public TRole GetBoundRoleByDevice(string deviceSN) { return m_info.ToRole(m_map.GetBoundRoleValueByDevice(deviceSN)); } public TRole GetBoundRoleByConnectedDevice(uint deviceIndex) { return m_info.ToRole(m_map.GetBoundRoleValueByConnectedDevice(deviceIndex)); } public string GetBoundDeviceByRole(TRole role) { return m_map.GetBoundDeviceByRoleValue(m_info.ToRoleValue(role)); } public string GetBoundDeviceByRoleValue(int roleValue) { return m_map.GetBoundDeviceByRoleValue(roleValue); } public int GetBoundRoleValueByDevice(string deviceSN) { return m_map.GetBoundRoleValueByDevice(deviceSN); } public int GetBoundRoleValueByConnectedDevice(uint deviceIndex) { return m_map.GetBoundRoleValueByConnectedDevice(deviceIndex); } } private static IndexedTable s_mapTable; private static Map GetInternalMap(Type roleType) { if (s_mapTable == null) { s_mapTable = new IndexedTable(); } Map map; if (!s_mapTable.TryGetValue(roleType, out map)) { var validateResult = ViveRoleEnum.ValidateViveRoleEnum(roleType); if (validateResult != ViveRoleEnumValidateResult.Valid) { Debug.LogWarning(roleType.Name + " is not valid ViveRole type. " + validateResult); return null; } map = new Map(roleType); s_mapTable.Add(roleType, map); } return map; } public static IMap GetMap(Type roleType) { return GetInternalMap(roleType); } private static GenericMap GetInternalMap() { var roleEnumType = typeof(TRole); if (GenericMap.s_instance == null) { var validateResult = ViveRoleEnum.ValidateViveRoleEnum(roleEnumType); if (validateResult != ViveRoleEnumValidateResult.Valid) { Debug.LogWarning(roleEnumType.Name + " is not valid ViveRole type. " + validateResult); return null; } new GenericMap(); } return GenericMap.s_instance; } public static IMap GetMap() { return GetInternalMap(); } public static void AssignMapHandler(MapHandler mapHandler) { Initialize(); GetInternalMap(typeof(TRole)).Handler = mapHandler; } } }