| using System; |
| using System.Collections.Generic; |
| using System.Diagnostics; |
| using System.Linq; |
| using Unity.Collections; |
| using UnityEngine.InputSystem.Composites; |
| using UnityEngine.InputSystem.Controls; |
| using Unity.Collections.LowLevel.Unsafe; |
| using UnityEngine.Profiling; |
| using UnityEngine.InputSystem.LowLevel; |
| using UnityEngine.InputSystem.Processors; |
| using UnityEngine.InputSystem.Interactions; |
| using UnityEngine.InputSystem.Utilities; |
| using UnityEngine.InputSystem.Layouts; |
|
|
| #if UNITY_EDITOR |
| using UnityEngine.InputSystem.Editor; |
| #endif |
|
|
| #if UNITY_EDITOR |
| using CustomBindingPathValidator = System.Func<string, System.Action>; |
| #endif |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| #pragma warning disable CS0649 |
| namespace UnityEngine.InputSystem |
| { |
| using DeviceChangeListener = Action<InputDevice, InputDeviceChange>; |
| using DeviceStateChangeListener = Action<InputDevice, InputEventPtr>; |
| using LayoutChangeListener = Action<string, InputControlLayoutChange>; |
| using EventListener = Action<InputEventPtr, InputDevice>; |
| using UpdateListener = Action; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| internal partial class InputManager |
| { |
| public ReadOnlyArray<InputDevice> devices => new ReadOnlyArray<InputDevice>(m_Devices, 0, m_DevicesCount); |
|
|
| public TypeTable processors => m_Processors; |
| public TypeTable interactions => m_Interactions; |
| public TypeTable composites => m_Composites; |
|
|
| public InputMetrics metrics |
| { |
| get |
| { |
| var result = m_Metrics; |
|
|
| result.currentNumDevices = m_DevicesCount; |
| result.currentStateSizeInBytes = (int)m_StateBuffers.totalSize; |
|
|
| |
| result.currentControlCount = m_DevicesCount; |
| for (var i = 0; i < m_DevicesCount; ++i) |
| result.currentControlCount += m_Devices[i].allControls.Count; |
|
|
| |
| result.currentLayoutCount = m_Layouts.layoutTypes.Count; |
| result.currentLayoutCount += m_Layouts.layoutStrings.Count; |
| result.currentLayoutCount += m_Layouts.layoutBuilders.Count; |
| result.currentLayoutCount += m_Layouts.layoutOverrides.Count; |
|
|
| return result; |
| } |
| } |
|
|
| public InputSettings settings |
| { |
| get |
| { |
| Debug.Assert(m_Settings != null); |
| return m_Settings; |
| } |
| set |
| { |
| if (value == null) |
| throw new ArgumentNullException(nameof(value)); |
|
|
| if (m_Settings == value) |
| return; |
|
|
| m_Settings = value; |
| ApplySettings(); |
| } |
| } |
|
|
| public InputUpdateType updateMask |
| { |
| get => m_UpdateMask; |
| set |
| { |
| |
| #if UNITY_EDITOR |
| value |= InputUpdateType.Editor; |
| #endif |
|
|
| if (m_UpdateMask == value) |
| return; |
|
|
| m_UpdateMask = value; |
|
|
| |
| if (m_DevicesCount > 0) |
| ReallocateStateBuffers(); |
| } |
| } |
|
|
| public InputUpdateType defaultUpdateType |
| { |
| get |
| { |
| if (m_CurrentUpdate != default) |
| return m_CurrentUpdate; |
|
|
| #if UNITY_EDITOR |
| if (!m_RunPlayerUpdatesInEditMode && (!gameIsPlaying || !gameHasFocus)) |
| return InputUpdateType.Editor; |
| #endif |
|
|
| return m_UpdateMask.GetUpdateTypeForPlayer(); |
| } |
| } |
|
|
| public float pollingFrequency |
| { |
| get => m_PollingFrequency; |
| set |
| { |
| |
| if (value <= 0) |
| throw new ArgumentException("Polling frequency must be greater than zero", "value"); |
|
|
| m_PollingFrequency = value; |
| if (m_Runtime != null) |
| m_Runtime.pollingFrequency = value; |
| } |
| } |
|
|
| public event DeviceChangeListener onDeviceChange |
| { |
| add => m_DeviceChangeListeners.AddCallback(value); |
| remove => m_DeviceChangeListeners.RemoveCallback(value); |
| } |
|
|
| public event DeviceStateChangeListener onDeviceStateChange |
| { |
| add => m_DeviceStateChangeListeners.AddCallback(value); |
| remove => m_DeviceStateChangeListeners.RemoveCallback(value); |
| } |
|
|
| public event InputDeviceCommandDelegate onDeviceCommand |
| { |
| add => m_DeviceCommandCallbacks.AddCallback(value); |
| remove => m_DeviceCommandCallbacks.RemoveCallback(value); |
| } |
|
|
| |
| public event InputDeviceFindControlLayoutDelegate onFindControlLayoutForDevice |
| { |
| add |
| { |
| m_DeviceFindLayoutCallbacks.AddCallback(value); |
|
|
| |
| |
| |
| |
| |
| |
| |
| AddAvailableDevicesThatAreNowRecognized(); |
| } |
| remove => m_DeviceFindLayoutCallbacks.RemoveCallback(value); |
| } |
|
|
| public event LayoutChangeListener onLayoutChange |
| { |
| add => m_LayoutChangeListeners.AddCallback(value); |
| remove => m_LayoutChangeListeners.RemoveCallback(value); |
| } |
|
|
| |
| |
| |
| public event EventListener onEvent |
| { |
| add => m_EventListeners.AddCallback(value); |
| remove => m_EventListeners.RemoveCallback(value); |
| } |
|
|
| public event UpdateListener onBeforeUpdate |
| { |
| add |
| { |
| InstallBeforeUpdateHookIfNecessary(); |
| m_BeforeUpdateListeners.AddCallback(value); |
| } |
| remove => m_BeforeUpdateListeners.RemoveCallback(value); |
| } |
|
|
| public event UpdateListener onAfterUpdate |
| { |
| add => m_AfterUpdateListeners.AddCallback(value); |
| remove => m_AfterUpdateListeners.RemoveCallback(value); |
| } |
|
|
| public event Action onSettingsChange |
| { |
| add => m_SettingsChangedListeners.AddCallback(value); |
| remove => m_SettingsChangedListeners.RemoveCallback(value); |
| } |
|
|
| public bool isProcessingEvents => m_InputEventStream.isOpen; |
|
|
| #if UNITY_EDITOR |
| |
| |
| |
| |
| |
| |
| |
| |
| internal event CustomBindingPathValidator customBindingPathValidators |
| { |
| add => m_customBindingPathValidators.AddCallback(value); |
| remove => m_customBindingPathValidators.RemoveCallback(value); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| internal void OnDrawCustomWarningForBindingPath(string bindingPath) |
| { |
| DelegateHelpers.InvokeCallbacksSafe_AndInvokeReturnedActions( |
| ref m_customBindingPathValidators, |
| bindingPath, |
| "InputSystem.OnDrawCustomWarningForBindingPath"); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| internal bool ShouldDrawWarningIconForBinding(string bindingPath) |
| { |
| return DelegateHelpers.InvokeCallbacksSafe_AnyCallbackReturnsObject( |
| ref m_customBindingPathValidators, |
| bindingPath, |
| "InputSystem.ShouldDrawWarningIconForBinding"); |
| } |
|
|
| #endif // UNITY_EDITOR |
|
|
| #if UNITY_EDITOR |
| private bool m_RunPlayerUpdatesInEditMode; |
|
|
| |
| |
| |
| |
| |
| |
| |
| public bool runPlayerUpdatesInEditMode |
| { |
| get => m_RunPlayerUpdatesInEditMode; |
| set => m_RunPlayerUpdatesInEditMode = value; |
| } |
| #endif |
|
|
| private bool gameIsPlaying => |
| #if UNITY_EDITOR |
| (m_Runtime.isInPlayMode && !m_Runtime.isPaused) || m_RunPlayerUpdatesInEditMode; |
| #else |
| true; |
| #endif |
|
|
| private bool gameHasFocus => |
| #if UNITY_EDITOR |
| m_RunPlayerUpdatesInEditMode || m_HasFocus || gameShouldGetInputRegardlessOfFocus; |
| #else |
| m_HasFocus || gameShouldGetInputRegardlessOfFocus; |
| #endif |
|
|
| private bool gameShouldGetInputRegardlessOfFocus => |
| m_Settings.backgroundBehavior == InputSettings.BackgroundBehavior.IgnoreFocus |
| #if UNITY_EDITOR |
| && m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView |
| #endif |
| ; |
|
|
| |
| |
|
|
| |
| |
| |
| public void RegisterControlLayout(string name, Type type) |
| { |
| if (string.IsNullOrEmpty(name)) |
| throw new ArgumentNullException(nameof(name)); |
| if (type == null) |
| throw new ArgumentNullException(nameof(type)); |
|
|
| |
| |
| var isDeviceLayout = typeof(InputDevice).IsAssignableFrom(type); |
| var isControlLayout = typeof(InputControl).IsAssignableFrom(type); |
|
|
| if (!isDeviceLayout && !isControlLayout) |
| throw new ArgumentException($"Types used as layouts have to be InputControls or InputDevices; '{type.Name}' is a '{type.BaseType.Name}'", |
| nameof(type)); |
|
|
| var internedName = new InternedString(name); |
| var isReplacement = m_Layouts.HasLayout(internedName); |
|
|
| |
| |
| |
| |
| |
| m_Layouts.layoutTypes[internedName] = type; |
|
|
| |
| |
|
|
| |
| |
| |
| string baseLayout = null; |
| for (var baseType = type.BaseType; baseLayout == null && baseType != typeof(InputControl); |
| baseType = baseType.BaseType) |
| { |
| foreach (var entry in m_Layouts.layoutTypes) |
| if (entry.Value == baseType) |
| { |
| baseLayout = entry.Key; |
| break; |
| } |
| } |
|
|
| PerformLayoutPostRegistration(internedName, new InlinedArray<InternedString>(new InternedString(baseLayout)), |
| isReplacement, isKnownToBeDeviceLayout: isDeviceLayout); |
| } |
|
|
| public void RegisterControlLayout(string json, string name = null, bool isOverride = false) |
| { |
| if (string.IsNullOrEmpty(json)) |
| throw new ArgumentNullException(nameof(json)); |
|
|
| |
|
|
| |
| InputControlLayout.ParseHeaderFieldsFromJson(json, out var nameFromJson, out var baseLayouts, |
| out var deviceMatcher); |
|
|
| |
| var internedLayoutName = new InternedString(name); |
| if (internedLayoutName.IsEmpty()) |
| { |
| internedLayoutName = nameFromJson; |
|
|
| |
| if (internedLayoutName.IsEmpty()) |
| throw new ArgumentException("Layout name has not been given and is not set in JSON layout", |
| nameof(name)); |
| } |
|
|
| |
| if (isOverride && baseLayouts.length == 0) |
| { |
| throw new ArgumentException( |
| $"Layout override '{internedLayoutName}' must have 'extend' property mentioning layout to which to apply the overrides", |
| nameof(json)); |
| } |
|
|
| |
| var isReplacement = m_Layouts.HasLayout(internedLayoutName); |
| if (isReplacement && isOverride) |
| { |
| |
| |
| |
|
|
| var isReplacingOverride = m_Layouts.layoutOverrideNames.Contains(internedLayoutName); |
| if (!isReplacingOverride) |
| { |
| throw new ArgumentException($"Failed to register layout override '{internedLayoutName}'" + |
| $"since a layout named '{internedLayoutName}' already exist. Layout overrides must " + |
| $"have unique names with respect to existing layouts."); |
| } |
| } |
|
|
| m_Layouts.layoutStrings[internedLayoutName] = json; |
| if (isOverride) |
| { |
| m_Layouts.layoutOverrideNames.Add(internedLayoutName); |
| for (var i = 0; i < baseLayouts.length; ++i) |
| { |
| var baseLayoutName = baseLayouts[i]; |
| m_Layouts.layoutOverrides.TryGetValue(baseLayoutName, out var overrideList); |
| if (!isReplacement) |
| ArrayHelpers.Append(ref overrideList, internedLayoutName); |
|
|
|
|
| m_Layouts.layoutOverrides[baseLayoutName] = overrideList; |
| } |
| } |
|
|
| PerformLayoutPostRegistration(internedLayoutName, baseLayouts, |
| isReplacement: isReplacement, isOverride: isOverride); |
|
|
| |
| if (!deviceMatcher.empty) |
| RegisterControlLayoutMatcher(internedLayoutName, deviceMatcher); |
| } |
|
|
| public void RegisterControlLayoutBuilder(Func<InputControlLayout> method, string name, |
| string baseLayout = null) |
| { |
| if (method == null) |
| throw new ArgumentNullException(nameof(method)); |
| if (string.IsNullOrEmpty(name)) |
| throw new ArgumentNullException(nameof(name)); |
|
|
| var internedLayoutName = new InternedString(name); |
| var internedBaseLayoutName = new InternedString(baseLayout); |
| var isReplacement = m_Layouts.HasLayout(internedLayoutName); |
|
|
| m_Layouts.layoutBuilders[internedLayoutName] = method; |
|
|
| PerformLayoutPostRegistration(internedLayoutName, new InlinedArray<InternedString>(internedBaseLayoutName), |
| isReplacement); |
| } |
|
|
| private void PerformLayoutPostRegistration(InternedString layoutName, InlinedArray<InternedString> baseLayouts, |
| bool isReplacement, bool isKnownToBeDeviceLayout = false, bool isOverride = false) |
| { |
| ++m_LayoutRegistrationVersion; |
|
|
| |
| |
| |
| InputControlLayout.s_CacheInstance.Clear(); |
|
|
| |
| |
| if (!isOverride && baseLayouts.length > 0) |
| { |
| if (baseLayouts.length > 1) |
| throw new NotSupportedException( |
| $"Layout '{layoutName}' has multiple base layouts; this is only supported on layout overrides"); |
|
|
| var baseLayoutName = baseLayouts[0]; |
| if (!baseLayoutName.IsEmpty()) |
| m_Layouts.baseLayoutTable[layoutName] = baseLayoutName; |
| } |
|
|
| |
| m_Layouts.precompiledLayouts.Remove(layoutName); |
| if (m_Layouts.precompiledLayouts.Count > 0) |
| { |
| foreach (var layout in m_Layouts.precompiledLayouts.Keys.ToArray()) |
| { |
| var metadata = m_Layouts.precompiledLayouts[layout].metadata; |
|
|
| |
| if (isOverride) |
| { |
| for (var i = 0; i < baseLayouts.length; ++i) |
| if (layout == baseLayouts[i] || |
| StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(metadata, |
| baseLayouts[i], ';')) |
| m_Layouts.precompiledLayouts.Remove(layout); |
| } |
| else |
| { |
| |
| if (StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(metadata, |
| layoutName, ';')) |
| m_Layouts.precompiledLayouts.Remove(layout); |
| } |
| } |
| } |
|
|
| |
| if (isOverride) |
| { |
| for (var i = 0; i < baseLayouts.length; ++i) |
| RecreateDevicesUsingLayout(baseLayouts[i], isKnownToBeDeviceLayout: isKnownToBeDeviceLayout); |
| } |
| else |
| { |
| RecreateDevicesUsingLayout(layoutName, isKnownToBeDeviceLayout: isKnownToBeDeviceLayout); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| #if UNITY_EDITOR |
| for (var i = 0; i < m_SavedDeviceStates.LengthSafe(); ++i) |
| { |
| ref var deviceState = ref m_SavedDeviceStates[i]; |
| if (layoutName != deviceState.layout || !deviceState.description.empty) |
| continue; |
|
|
| if (RestoreDeviceFromSavedState(ref deviceState, layoutName)) |
| { |
| ArrayHelpers.EraseAt(ref m_SavedDeviceStates, i); |
| --i; |
| } |
| } |
| #endif |
|
|
| |
| var change = isReplacement ? InputControlLayoutChange.Replaced : InputControlLayoutChange.Added; |
| DelegateHelpers.InvokeCallbacksSafe(ref m_LayoutChangeListeners, layoutName.ToString(), change, "InputSystem.onLayoutChange"); |
| } |
|
|
| public void RegisterPrecompiledLayout<TDevice>(string metadata) |
| where TDevice : InputDevice, new() |
| { |
| if (metadata == null) |
| throw new ArgumentNullException(nameof(metadata)); |
|
|
| var deviceType = typeof(TDevice).BaseType; |
| var layoutName = FindOrRegisterDeviceLayoutForType(deviceType); |
|
|
| m_Layouts.precompiledLayouts[layoutName] = new InputControlLayout.Collection.PrecompiledLayout |
| { |
| factoryMethod = () => new TDevice(), |
| metadata = metadata |
| }; |
| } |
|
|
| private void RecreateDevicesUsingLayout(InternedString layout, bool isKnownToBeDeviceLayout = false) |
| { |
| if (m_DevicesCount == 0) |
| return; |
|
|
| List<InputDevice> devicesUsingLayout = null; |
|
|
| |
| for (var i = 0; i < m_DevicesCount; ++i) |
| { |
| var device = m_Devices[i]; |
|
|
| bool usesLayout; |
| if (isKnownToBeDeviceLayout) |
| usesLayout = IsControlUsingLayout(device, layout); |
| else |
| usesLayout = IsControlOrChildUsingLayoutRecursive(device, layout); |
|
|
| if (usesLayout) |
| { |
| if (devicesUsingLayout == null) |
| devicesUsingLayout = new List<InputDevice>(); |
| devicesUsingLayout.Add(device); |
| } |
| } |
|
|
| |
| if (devicesUsingLayout == null) |
| return; |
|
|
| |
| using (InputDeviceBuilder.Ref()) |
| { |
| for (var i = 0; i < devicesUsingLayout.Count; ++i) |
| { |
| var device = devicesUsingLayout[i]; |
| RecreateDevice(device, device.m_Layout); |
| } |
| } |
| } |
|
|
| private bool IsControlOrChildUsingLayoutRecursive(InputControl control, InternedString layout) |
| { |
| |
| if (IsControlUsingLayout(control, layout)) |
| return true; |
|
|
| |
| var children = control.children; |
| for (var i = 0; i < children.Count; ++i) |
| if (IsControlOrChildUsingLayoutRecursive(children[i], layout)) |
| return true; |
|
|
| return false; |
| } |
|
|
| private bool IsControlUsingLayout(InputControl control, InternedString layout) |
| { |
| |
| if (control.layout == layout) |
| return true; |
|
|
| |
| var baseLayout = control.m_Layout; |
| while (m_Layouts.baseLayoutTable.TryGetValue(baseLayout, out baseLayout)) |
| if (baseLayout == layout) |
| return true; |
|
|
| return false; |
| } |
|
|
| public void RegisterControlLayoutMatcher(string layoutName, InputDeviceMatcher matcher) |
| { |
| if (string.IsNullOrEmpty(layoutName)) |
| throw new ArgumentNullException(nameof(layoutName)); |
| if (matcher.empty) |
| throw new ArgumentException("Matcher cannot be empty", nameof(matcher)); |
|
|
| |
| var internedLayoutName = new InternedString(layoutName); |
| m_Layouts.AddMatcher(internedLayoutName, matcher); |
|
|
| |
| RecreateDevicesUsingLayoutWithInferiorMatch(matcher); |
|
|
| |
| AddAvailableDevicesMatchingDescription(matcher, internedLayoutName); |
| } |
|
|
| public void RegisterControlLayoutMatcher(Type type, InputDeviceMatcher matcher) |
| { |
| if (type == null) |
| throw new ArgumentNullException(nameof(type)); |
| if (matcher.empty) |
| throw new ArgumentException("Matcher cannot be empty", nameof(matcher)); |
|
|
| var layoutName = m_Layouts.TryFindLayoutForType(type); |
| if (layoutName.IsEmpty()) |
| throw new ArgumentException( |
| $"Type '{type.Name}' has not been registered as a control layout", nameof(type)); |
|
|
| RegisterControlLayoutMatcher(layoutName, matcher); |
| } |
|
|
| private void RecreateDevicesUsingLayoutWithInferiorMatch(InputDeviceMatcher deviceMatcher) |
| { |
| if (m_DevicesCount == 0) |
| return; |
|
|
| using (InputDeviceBuilder.Ref()) |
| { |
| var deviceCount = m_DevicesCount; |
| for (var i = 0; i < deviceCount; ++i) |
| { |
| var device = m_Devices[i]; |
| var deviceDescription = device.description; |
|
|
| if (deviceDescription.empty || !(deviceMatcher.MatchPercentage(deviceDescription) > 0)) |
| continue; |
|
|
| var layoutName = TryFindMatchingControlLayout(ref deviceDescription, device.deviceId); |
| if (layoutName != device.m_Layout) |
| { |
| device.m_Description = deviceDescription; |
|
|
| RecreateDevice(device, layoutName); |
|
|
| |
| |
| |
|
|
| --i; |
| --deviceCount; |
| } |
| } |
| } |
| } |
|
|
| private void RecreateDevice(InputDevice oldDevice, InternedString newLayout) |
| { |
| |
| RemoveDevice(oldDevice, keepOnListOfAvailableDevices: true); |
|
|
| |
| var newDevice = InputDevice.Build<InputDevice>(newLayout, oldDevice.m_Variants, |
| deviceDescription: oldDevice.m_Description); |
|
|
| |
| |
| newDevice.m_DeviceId = oldDevice.m_DeviceId; |
| newDevice.m_Description = oldDevice.m_Description; |
| if (oldDevice.native) |
| newDevice.m_DeviceFlags |= InputDevice.DeviceFlags.Native; |
| if (oldDevice.remote) |
| newDevice.m_DeviceFlags |= InputDevice.DeviceFlags.Remote; |
| if (!oldDevice.enabled) |
| { |
| newDevice.m_DeviceFlags |= InputDevice.DeviceFlags.DisabledStateHasBeenQueriedFromRuntime; |
| newDevice.m_DeviceFlags |= InputDevice.DeviceFlags.DisabledInFrontend; |
| } |
|
|
| |
| AddDevice(newDevice); |
| } |
|
|
| private void AddAvailableDevicesMatchingDescription(InputDeviceMatcher matcher, InternedString layout) |
| { |
| #if UNITY_EDITOR |
| |
| |
| for (var i = 0; i < m_SavedDeviceStates.LengthSafe(); ++i) |
| { |
| ref var deviceState = ref m_SavedDeviceStates[i]; |
| if (matcher.MatchPercentage(deviceState.description) > 0) |
| { |
| RestoreDeviceFromSavedState(ref deviceState, layout); |
| ArrayHelpers.EraseAt(ref m_SavedDeviceStates, i); |
| --i; |
| } |
| } |
| #endif |
|
|
| |
| |
| for (var i = 0; i < m_AvailableDeviceCount; ++i) |
| { |
| |
| if (m_AvailableDevices[i].isRemoved) |
| continue; |
|
|
| var deviceId = m_AvailableDevices[i].deviceId; |
| if (TryGetDeviceById(deviceId) != null) |
| continue; |
|
|
| if (matcher.MatchPercentage(m_AvailableDevices[i].description) > 0f) |
| { |
| |
| try |
| { |
| AddDevice(layout, deviceId, deviceDescription: m_AvailableDevices[i].description, |
| deviceFlags: m_AvailableDevices[i].isNative ? InputDevice.DeviceFlags.Native : 0); |
| } |
| catch (Exception exception) |
| { |
| Debug.LogError( |
| $"Layout '{layout}' matches existing device '{m_AvailableDevices[i].description}' but failed to instantiate: {exception}"); |
| Debug.LogException(exception); |
| continue; |
| } |
|
|
| |
| var command = EnableDeviceCommand.Create(); |
| m_Runtime.DeviceCommand(deviceId, ref command); |
| } |
| } |
| } |
|
|
| public void RemoveControlLayout(string name) |
| { |
| if (string.IsNullOrEmpty(name)) |
| throw new ArgumentNullException(nameof(name)); |
|
|
| var internedName = new InternedString(name); |
|
|
| |
| for (var i = 0; i < m_DevicesCount;) |
| { |
| var device = m_Devices[i]; |
| if (IsControlOrChildUsingLayoutRecursive(device, internedName)) |
| { |
| RemoveDevice(device, keepOnListOfAvailableDevices: true); |
| } |
| else |
| { |
| ++i; |
| } |
| } |
|
|
| |
| m_Layouts.layoutTypes.Remove(internedName); |
| m_Layouts.layoutStrings.Remove(internedName); |
| m_Layouts.layoutBuilders.Remove(internedName); |
| m_Layouts.baseLayoutTable.Remove(internedName); |
| ++m_LayoutRegistrationVersion; |
|
|
| |
| |
|
|
| |
| DelegateHelpers.InvokeCallbacksSafe(ref m_LayoutChangeListeners, name, InputControlLayoutChange.Removed, "InputSystem.onLayoutChange"); |
| } |
|
|
| public InputControlLayout TryLoadControlLayout(Type type) |
| { |
| if (type == null) |
| throw new ArgumentNullException(nameof(type)); |
| if (!typeof(InputControl).IsAssignableFrom(type)) |
| throw new ArgumentException($"Type '{type.Name}' is not an InputControl", nameof(type)); |
|
|
| |
| var layoutName = m_Layouts.TryFindLayoutForType(type); |
| if (layoutName.IsEmpty()) |
| throw new ArgumentException( |
| $"Type '{type.Name}' has not been registered as a control layout", nameof(type)); |
|
|
| return m_Layouts.TryLoadLayout(layoutName); |
| } |
|
|
| public InputControlLayout TryLoadControlLayout(InternedString name) |
| { |
| return m_Layouts.TryLoadLayout(name); |
| } |
|
|
| |
| public InternedString TryFindMatchingControlLayout(ref InputDeviceDescription deviceDescription, int deviceId = InputDevice.InvalidDeviceId) |
| { |
| Profiler.BeginSample("InputSystem.TryFindMatchingControlLayout"); |
| |
|
|
| |
| var layoutName = m_Layouts.TryFindMatchingLayout(deviceDescription); |
| if (layoutName.IsEmpty()) |
| { |
| |
| |
| |
| |
| |
| |
| if (!string.IsNullOrEmpty(deviceDescription.deviceClass)) |
| { |
| var deviceClassLowerCase = new InternedString(deviceDescription.deviceClass); |
| var type = m_Layouts.GetControlTypeForLayout(deviceClassLowerCase); |
| if (type != null && typeof(InputDevice).IsAssignableFrom(type)) |
| layoutName = new InternedString(deviceDescription.deviceClass); |
| } |
| } |
|
|
| |
| |
| |
| if (m_DeviceFindLayoutCallbacks.length > 0) |
| { |
| |
| |
| |
| if (m_DeviceFindExecuteCommandDelegate == null) |
| m_DeviceFindExecuteCommandDelegate = |
| (ref InputDeviceCommand commandRef) => |
| { |
| if (m_DeviceFindExecuteCommandDeviceId == InputDevice.InvalidDeviceId) |
| return InputDeviceCommand.GenericFailure; |
| return m_Runtime.DeviceCommand(m_DeviceFindExecuteCommandDeviceId, ref commandRef); |
| }; |
| m_DeviceFindExecuteCommandDeviceId = deviceId; |
|
|
| var haveOverriddenLayoutName = false; |
| m_DeviceFindLayoutCallbacks.LockForChanges(); |
| for (var i = 0; i < m_DeviceFindLayoutCallbacks.length; ++i) |
| { |
| try |
| { |
| var newLayout = m_DeviceFindLayoutCallbacks[i](ref deviceDescription, layoutName, m_DeviceFindExecuteCommandDelegate); |
| if (!string.IsNullOrEmpty(newLayout) && !haveOverriddenLayoutName) |
| { |
| layoutName = new InternedString(newLayout); |
| haveOverriddenLayoutName = true; |
| } |
| } |
| catch (Exception exception) |
| { |
| Debug.LogError($"{exception.GetType().Name} while executing 'InputSystem.onFindLayoutForDevice' callbacks"); |
| Debug.LogException(exception); |
| } |
| } |
| m_DeviceFindLayoutCallbacks.UnlockForChanges(); |
| } |
|
|
| Profiler.EndSample(); |
| return layoutName; |
| } |
|
|
| private InternedString FindOrRegisterDeviceLayoutForType(Type type) |
| { |
| var layoutName = m_Layouts.TryFindLayoutForType(type); |
| if (layoutName.IsEmpty()) |
| { |
| |
| if (layoutName.IsEmpty()) |
| { |
| layoutName = new InternedString(type.Name); |
| RegisterControlLayout(type.Name, type); |
| } |
| } |
|
|
| return layoutName; |
| } |
|
|
| |
| |
| |
| |
| |
| private bool IsDeviceLayoutMarkedAsSupportedInSettings(InternedString layoutName) |
| { |
| |
| |
| |
| |
| #if UNITY_EDITOR |
| if (InputEditorUserSettings.addDevicesNotSupportedByProject) |
| return true; |
| #endif |
|
|
| var supportedDevices = m_Settings.supportedDevices; |
| if (supportedDevices.Count == 0) |
| { |
| |
| return true; |
| } |
|
|
| for (var n = 0; n < supportedDevices.Count; ++n) |
| { |
| var supportedLayout = new InternedString(supportedDevices[n]); |
| if (layoutName == supportedLayout || m_Layouts.IsBasedOn(supportedLayout, layoutName)) |
| return true; |
| } |
|
|
| return false; |
| } |
|
|
| public IEnumerable<string> ListControlLayouts(string basedOn = null) |
| { |
| |
|
|
| if (!string.IsNullOrEmpty(basedOn)) |
| { |
| var internedBasedOn = new InternedString(basedOn); |
| foreach (var entry in m_Layouts.layoutTypes) |
| if (m_Layouts.IsBasedOn(internedBasedOn, entry.Key)) |
| yield return entry.Key; |
| foreach (var entry in m_Layouts.layoutStrings) |
| if (m_Layouts.IsBasedOn(internedBasedOn, entry.Key)) |
| yield return entry.Key; |
| foreach (var entry in m_Layouts.layoutBuilders) |
| if (m_Layouts.IsBasedOn(internedBasedOn, entry.Key)) |
| yield return entry.Key; |
| } |
| else |
| { |
| foreach (var entry in m_Layouts.layoutTypes) |
| yield return entry.Key; |
| foreach (var entry in m_Layouts.layoutStrings) |
| yield return entry.Key; |
| foreach (var entry in m_Layouts.layoutBuilders) |
| yield return entry.Key; |
| } |
| } |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| public int GetControls<TControl>(string path, ref InputControlList<TControl> controls) |
| where TControl : InputControl |
| { |
| if (string.IsNullOrEmpty(path)) |
| return 0; |
| if (m_DevicesCount == 0) |
| return 0; |
|
|
| var deviceCount = m_DevicesCount; |
| var numMatches = 0; |
| for (var i = 0; i < deviceCount; ++i) |
| { |
| var device = m_Devices[i]; |
| numMatches += InputControlPath.TryFindControls(device, path, 0, ref controls); |
| } |
|
|
| return numMatches; |
| } |
|
|
| public void SetDeviceUsage(InputDevice device, InternedString usage) |
| { |
| if (device == null) |
| throw new ArgumentNullException(nameof(device)); |
| if (device.usages.Count == 1 && device.usages[0] == usage) |
| return; |
| if (device.usages.Count == 0 && usage.IsEmpty()) |
| return; |
|
|
| device.ClearDeviceUsages(); |
| if (!usage.IsEmpty()) |
| device.AddDeviceUsage(usage); |
| NotifyUsageChanged(device); |
| } |
|
|
| public void AddDeviceUsage(InputDevice device, InternedString usage) |
| { |
| if (device == null) |
| throw new ArgumentNullException(nameof(device)); |
| if (usage.IsEmpty()) |
| throw new ArgumentException("Usage string cannot be empty", nameof(usage)); |
| if (device.usages.Contains(usage)) |
| return; |
|
|
| device.AddDeviceUsage(usage); |
| NotifyUsageChanged(device); |
| } |
|
|
| public void RemoveDeviceUsage(InputDevice device, InternedString usage) |
| { |
| if (device == null) |
| throw new ArgumentNullException(nameof(device)); |
| if (usage.IsEmpty()) |
| throw new ArgumentException("Usage string cannot be empty", nameof(usage)); |
| if (!device.usages.Contains(usage)) |
| return; |
|
|
| device.RemoveDeviceUsage(usage); |
| NotifyUsageChanged(device); |
| } |
|
|
| private void NotifyUsageChanged(InputDevice device) |
| { |
| InputActionState.OnDeviceChange(device, InputDeviceChange.UsageChanged); |
|
|
| |
| DelegateHelpers.InvokeCallbacksSafe(ref m_DeviceChangeListeners, device, InputDeviceChange.UsageChanged, "InputSystem.onDeviceChange"); |
|
|
| |
| |
| device.MakeCurrent(); |
| } |
|
|
| |
|
|
| public InputDevice AddDevice(Type type, string name = null) |
| { |
| if (type == null) |
| throw new ArgumentNullException(nameof(type)); |
|
|
| |
| var layoutName = FindOrRegisterDeviceLayoutForType(type); |
| Debug.Assert(!layoutName.IsEmpty(), name); |
|
|
| |
| |
| return AddDevice(layoutName, name); |
| } |
|
|
| |
| |
| public InputDevice AddDevice(string layout, string name = null, InternedString variants = new InternedString()) |
| { |
| if (string.IsNullOrEmpty(layout)) |
| throw new ArgumentNullException(nameof(layout)); |
|
|
| var device = InputDevice.Build<InputDevice>(layout, variants); |
|
|
| if (!string.IsNullOrEmpty(name)) |
| device.m_Name = new InternedString(name); |
|
|
| AddDevice(device); |
|
|
| return device; |
| } |
|
|
| |
| private InputDevice AddDevice(InternedString layout, int deviceId, |
| string deviceName = null, |
| InputDeviceDescription deviceDescription = new InputDeviceDescription(), |
| InputDevice.DeviceFlags deviceFlags = 0, |
| InternedString variants = default) |
| { |
| var device = InputDevice.Build<InputDevice>(new InternedString(layout), |
| deviceDescription: deviceDescription, |
| layoutVariants: variants); |
|
|
| device.m_DeviceId = deviceId; |
| device.m_Description = deviceDescription; |
| device.m_DeviceFlags |= deviceFlags; |
| if (!string.IsNullOrEmpty(deviceName)) |
| device.m_Name = new InternedString(deviceName); |
|
|
| |
| if (!string.IsNullOrEmpty(deviceDescription.product)) |
| device.m_DisplayName = deviceDescription.product; |
|
|
| AddDevice(device); |
|
|
| return device; |
| } |
|
|
| public void AddDevice(InputDevice device) |
| { |
| if (device == null) |
| throw new ArgumentNullException(nameof(device)); |
| if (string.IsNullOrEmpty(device.layout)) |
| throw new InvalidOperationException("Device has no associated layout"); |
|
|
| |
| if (ArrayHelpers.Contains(m_Devices, device)) |
| return; |
|
|
| MakeDeviceNameUnique(device); |
| AssignUniqueDeviceId(device); |
|
|
| |
| device.m_DeviceIndex = ArrayHelpers.AppendWithCapacity(ref m_Devices, ref m_DevicesCount, device); |
|
|
| |
| |
| |
| |
| |
| |
| m_DevicesById[device.deviceId] = device; |
|
|
| |
| device.m_StateBlock.byteOffset = InputStateBlock.InvalidOffset; |
|
|
| |
| ReallocateStateBuffers(); |
| InitializeDeviceState(device); |
|
|
| |
| m_Metrics.maxNumDevices = Mathf.Max(m_DevicesCount, m_Metrics.maxNumDevices); |
| m_Metrics.maxStateSizeInBytes = Mathf.Max((int)m_StateBuffers.totalSize, m_Metrics.maxStateSizeInBytes); |
|
|
| |
| |
| for (var i = 0; i < m_AvailableDeviceCount; ++i) |
| { |
| if (m_AvailableDevices[i].deviceId == device.deviceId) |
| m_AvailableDevices[i].isRemoved = false; |
| } |
|
|
| |
| |
| var isPlaying = true; |
| #if UNITY_EDITOR |
| isPlaying = m_Runtime.isInPlayMode; |
| #endif |
| if (isPlaying && !gameHasFocus |
| && m_Settings.backgroundBehavior != InputSettings.BackgroundBehavior.IgnoreFocus |
| && m_Runtime.runInBackground |
| && device.QueryEnabledStateFromRuntime() |
| && !ShouldRunDeviceInBackground(device)) |
| { |
| EnableOrDisableDevice(device, false, DeviceDisableScope.TemporaryWhilePlayerIsInBackground); |
| } |
|
|
| |
| |
| InputActionState.OnDeviceChange(device, InputDeviceChange.Added); |
|
|
| |
| |
| if (device is IInputUpdateCallbackReceiver beforeUpdateCallbackReceiver) |
| onBeforeUpdate += beforeUpdateCallbackReceiver.OnUpdate; |
|
|
| |
| if (device is IInputStateCallbackReceiver) |
| { |
| InstallBeforeUpdateHookIfNecessary(); |
| device.m_DeviceFlags |= InputDevice.DeviceFlags.HasStateCallbacks; |
| m_HaveDevicesWithStateCallbackReceivers = true; |
| } |
|
|
| |
| if (device is IEventMerger) |
| device.hasEventMerger = true; |
|
|
| |
| if (device is IEventPreProcessor) |
| device.hasEventPreProcessor = true; |
|
|
| |
| |
| if (device.updateBeforeRender) |
| updateMask |= InputUpdateType.BeforeRender; |
|
|
| |
| device.NotifyAdded(); |
|
|
| |
| |
| |
| |
| |
| device.MakeCurrent(); |
|
|
| |
| DelegateHelpers.InvokeCallbacksSafe(ref m_DeviceChangeListeners, device, InputDeviceChange.Added, "InputSystem.onDeviceChange"); |
|
|
| |
| if (device.enabled) |
| device.RequestSync(); |
|
|
| device.SetOptimizedControlDataTypeRecursively(); |
| } |
|
|
| |
| |
| public InputDevice AddDevice(InputDeviceDescription description) |
| { |
| |
| return AddDevice(description, throwIfNoLayoutFound: true); |
| } |
|
|
| public InputDevice AddDevice(InputDeviceDescription description, bool throwIfNoLayoutFound, |
| string deviceName = null, int deviceId = InputDevice.InvalidDeviceId, InputDevice.DeviceFlags deviceFlags = 0) |
| { |
| Profiler.BeginSample("InputSystem.AddDevice"); |
| |
| var layout = TryFindMatchingControlLayout(ref description, deviceId); |
|
|
| |
| if (layout.IsEmpty()) |
| { |
| if (throwIfNoLayoutFound) |
| throw new ArgumentException($"Cannot find layout matching device description '{description}'", nameof(description)); |
|
|
| |
| if (deviceId != InputDevice.InvalidDeviceId) |
| { |
| var command = DisableDeviceCommand.Create(); |
| m_Runtime.DeviceCommand(deviceId, ref command); |
| } |
|
|
| Profiler.EndSample(); |
| return null; |
| } |
|
|
| var device = AddDevice(layout, deviceId, deviceName, description, deviceFlags); |
| device.m_Description = description; |
| Profiler.EndSample(); |
| return device; |
| } |
|
|
| public InputDevice AddDevice(InputDeviceDescription description, InternedString layout, string deviceName = null, |
| int deviceId = InputDevice.InvalidDeviceId, InputDevice.DeviceFlags deviceFlags = 0) |
| { |
| try |
| { |
| Profiler.BeginSample("InputSystem.AddDevice"); |
|
|
| var device = AddDevice(layout, deviceId, deviceName, description, deviceFlags); |
| device.m_Description = description; |
| return device; |
| } |
| finally |
| { |
| Profiler.EndSample(); |
| } |
| } |
|
|
| public void RemoveDevice(InputDevice device, bool keepOnListOfAvailableDevices = false) |
| { |
| if (device == null) |
| throw new ArgumentNullException(nameof(device)); |
|
|
| |
| if (device.m_DeviceIndex == InputDevice.kInvalidDeviceIndex) |
| return; |
|
|
| |
| RemoveStateChangeMonitors(device); |
|
|
| |
| var deviceIndex = device.m_DeviceIndex; |
| var deviceId = device.deviceId; |
| if (deviceIndex < m_StateChangeMonitors.LengthSafe()) |
| { |
| |
| var count = m_StateChangeMonitors.Length; |
| ArrayHelpers.EraseAtWithCapacity(m_StateChangeMonitors, ref count, deviceIndex); |
| } |
| ArrayHelpers.EraseAtWithCapacity(m_Devices, ref m_DevicesCount, deviceIndex); |
|
|
| m_DevicesById.Remove(deviceId); |
|
|
| if (m_Devices != null) |
| { |
| |
| ReallocateStateBuffers(); |
| } |
| else |
| { |
| |
| m_StateBuffers.FreeAll(); |
| } |
|
|
| |
|
|
| |
| |
| for (var i = deviceIndex; i < m_DevicesCount; ++i) |
| --m_Devices[i].m_DeviceIndex; |
| device.m_DeviceIndex = InputDevice.kInvalidDeviceIndex; |
|
|
| |
| for (var i = 0; i < m_AvailableDeviceCount; ++i) |
| { |
| if (m_AvailableDevices[i].deviceId == deviceId) |
| { |
| if (keepOnListOfAvailableDevices) |
| m_AvailableDevices[i].isRemoved = true; |
| else |
| ArrayHelpers.EraseAtWithCapacity(m_AvailableDevices, ref m_AvailableDeviceCount, i); |
| break; |
| } |
| } |
|
|
| |
| device.BakeOffsetIntoStateBlockRecursive((uint)-device.m_StateBlock.byteOffset); |
|
|
| |
| |
| |
| InputActionState.OnDeviceChange(device, InputDeviceChange.Removed); |
|
|
| |
| if (device is IInputUpdateCallbackReceiver beforeUpdateCallbackReceiver) |
| onBeforeUpdate -= beforeUpdateCallbackReceiver.OnUpdate; |
|
|
| |
| |
| if (device.updateBeforeRender) |
| { |
| var haveDeviceRequiringBeforeRender = false; |
| for (var i = 0; i < m_DevicesCount; ++i) |
| if (m_Devices[i].updateBeforeRender) |
| { |
| haveDeviceRequiringBeforeRender = true; |
| break; |
| } |
|
|
| if (!haveDeviceRequiringBeforeRender) |
| updateMask &= ~InputUpdateType.BeforeRender; |
| } |
|
|
| |
| device.NotifyRemoved(); |
|
|
| |
| DelegateHelpers.InvokeCallbacksSafe(ref m_DeviceChangeListeners, device, InputDeviceChange.Removed, "InputSystem.onDeviceChange"); |
|
|
| |
| InputSystem.GetDevice(device.GetType())?.MakeCurrent(); |
| } |
|
|
| public void FlushDisconnectedDevices() |
| { |
| m_DisconnectedDevices.Clear(m_DisconnectedDevicesCount); |
| m_DisconnectedDevicesCount = 0; |
| } |
|
|
| public unsafe void ResetDevice(InputDevice device, bool alsoResetDontResetControls = false, bool? issueResetCommand = null) |
| { |
| if (device == null) |
| throw new ArgumentNullException(nameof(device)); |
| if (!device.added) |
| throw new InvalidOperationException($"Device '{device}' has not been added to the system"); |
|
|
| var isHardReset = alsoResetDontResetControls || !device.hasDontResetControls; |
|
|
| |
| var change = isHardReset ? InputDeviceChange.HardReset : InputDeviceChange.SoftReset; |
| InputActionState.OnDeviceChange(device, change); |
| DelegateHelpers.InvokeCallbacksSafe(ref m_DeviceChangeListeners, device, change, "onDeviceChange"); |
|
|
| |
| if (!alsoResetDontResetControls && device is ICustomDeviceReset customReset) |
| { |
| customReset.Reset(); |
| } |
| else |
| { |
| var defaultStatePtr = device.defaultStatePtr; |
| var deviceStateBlockSize = device.stateBlock.alignedSizeInBytes; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| using (var tempBuffer = |
| new NativeArray<byte>(InputEvent.kBaseEventSize + sizeof(int) + (int)deviceStateBlockSize, Allocator.Temp)) |
| { |
| var stateEventPtr = (StateEvent*)tempBuffer.GetUnsafePtr(); |
| var statePtr = stateEventPtr->state; |
| var currentTime = m_Runtime.currentTime; |
|
|
| |
| ref var stateBlock = ref device.m_StateBlock; |
| stateEventPtr->baseEvent.type = StateEvent.Type; |
| stateEventPtr->baseEvent.sizeInBytes = InputEvent.kBaseEventSize + sizeof(int) + deviceStateBlockSize; |
| stateEventPtr->baseEvent.time = currentTime; |
| stateEventPtr->baseEvent.deviceId = device.deviceId; |
| stateEventPtr->baseEvent.eventId = -1; |
| stateEventPtr->stateFormat = device.m_StateBlock.format; |
|
|
| |
| if (isHardReset) |
| { |
| |
| |
| UnsafeUtility.MemCpy(statePtr, |
| (byte*)defaultStatePtr + stateBlock.byteOffset, |
| deviceStateBlockSize); |
| } |
| else |
| { |
| |
| |
|
|
| var currentStatePtr = device.currentStatePtr; |
| var resetMaskPtr = m_StateBuffers.resetMaskBuffer; |
|
|
| |
| UnsafeUtility.MemCpy(statePtr, |
| (byte*)currentStatePtr + stateBlock.byteOffset, |
| deviceStateBlockSize); |
|
|
| |
| MemoryHelpers.MemCpyMasked(statePtr, |
| (byte*)defaultStatePtr + stateBlock.byteOffset, |
| (int)deviceStateBlockSize, |
| (byte*)resetMaskPtr + stateBlock.byteOffset); |
| } |
|
|
| UpdateState(device, defaultUpdateType, statePtr, 0, deviceStateBlockSize, currentTime, |
| new InputEventPtr((InputEvent*)stateEventPtr)); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| var doIssueResetCommand = isHardReset; |
| if (issueResetCommand != null) |
| doIssueResetCommand = issueResetCommand.Value; |
| #if UNITY_EDITOR |
| else if (m_Settings.editorInputBehaviorInPlayMode != InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView) |
| doIssueResetCommand = false; |
| #endif |
|
|
| if (doIssueResetCommand) |
| device.RequestReset(); |
| } |
|
|
| public InputDevice TryGetDevice(string nameOrLayout) |
| { |
| if (string.IsNullOrEmpty(nameOrLayout)) |
| throw new ArgumentException("Name is null or empty.", nameof(nameOrLayout)); |
|
|
| if (m_DevicesCount == 0) |
| return null; |
|
|
| var nameOrLayoutLowerCase = nameOrLayout.ToLower(); |
|
|
| for (var i = 0; i < m_DevicesCount; ++i) |
| { |
| var device = m_Devices[i]; |
| if (device.m_Name.ToLower() == nameOrLayoutLowerCase || |
| device.m_Layout.ToLower() == nameOrLayoutLowerCase) |
| return device; |
| } |
|
|
| return null; |
| } |
|
|
| public InputDevice GetDevice(string nameOrLayout) |
| { |
| var device = TryGetDevice(nameOrLayout); |
| if (device == null) |
| throw new ArgumentException($"Cannot find device with name or layout '{nameOrLayout}'", nameof(nameOrLayout)); |
|
|
| return device; |
| } |
|
|
| public InputDevice TryGetDevice(Type layoutType) |
| { |
| var layoutName = m_Layouts.TryFindLayoutForType(layoutType); |
| if (layoutName.IsEmpty()) |
| return null; |
|
|
| return TryGetDevice(layoutName); |
| } |
|
|
| public InputDevice TryGetDeviceById(int id) |
| { |
| if (m_DevicesById.TryGetValue(id, out var result)) |
| return result; |
| return null; |
| } |
|
|
| |
| |
| public int GetUnsupportedDevices(List<InputDeviceDescription> descriptions) |
| { |
| if (descriptions == null) |
| throw new ArgumentNullException(nameof(descriptions)); |
|
|
| var numFound = 0; |
| for (var i = 0; i < m_AvailableDeviceCount; ++i) |
| { |
| if (TryGetDeviceById(m_AvailableDevices[i].deviceId) != null) |
| continue; |
|
|
| descriptions.Add(m_AvailableDevices[i].description); |
| ++numFound; |
| } |
|
|
| return numFound; |
| } |
|
|
| |
| internal enum DeviceDisableScope |
| { |
| Everywhere, |
| InFrontendOnly, |
| TemporaryWhilePlayerIsInBackground, |
| } |
|
|
| public void EnableOrDisableDevice(InputDevice device, bool enable, DeviceDisableScope scope = default) |
| { |
| if (device == null) |
| throw new ArgumentNullException(nameof(device)); |
|
|
| |
| if (enable) |
| { |
| |
|
|
| |
| switch (scope) |
| { |
| case DeviceDisableScope.Everywhere: |
| device.disabledWhileInBackground = false; |
| if (!device.disabledInFrontend && !device.disabledInRuntime) |
| return; |
| if (device.disabledInRuntime) |
| { |
| device.ExecuteEnableCommand(); |
| device.disabledInRuntime = false; |
| } |
| if (device.disabledInFrontend) |
| { |
| if (!device.RequestSync()) |
| ResetDevice(device); |
| device.disabledInFrontend = false; |
| } |
| break; |
|
|
| case DeviceDisableScope.InFrontendOnly: |
| device.disabledWhileInBackground = false; |
| if (!device.disabledInFrontend && device.disabledInRuntime) |
| return; |
| if (!device.disabledInRuntime) |
| { |
| device.ExecuteDisableCommand(); |
| device.disabledInRuntime = true; |
| } |
| if (device.disabledInFrontend) |
| { |
| if (!device.RequestSync()) |
| ResetDevice(device); |
| device.disabledInFrontend = false; |
| } |
| break; |
|
|
| case DeviceDisableScope.TemporaryWhilePlayerIsInBackground: |
| if (device.disabledWhileInBackground) |
| { |
| if (device.disabledInRuntime) |
| { |
| device.ExecuteEnableCommand(); |
| device.disabledInRuntime = false; |
| } |
| if (!device.RequestSync()) |
| ResetDevice(device); |
| device.disabledWhileInBackground = false; |
| } |
| break; |
| } |
| } |
| else |
| { |
| |
| switch (scope) |
| { |
| case DeviceDisableScope.Everywhere: |
| device.disabledWhileInBackground = false; |
| if (device.disabledInFrontend && device.disabledInRuntime) |
| return; |
| if (!device.disabledInRuntime) |
| { |
| device.ExecuteDisableCommand(); |
| device.disabledInRuntime = true; |
| } |
| if (!device.disabledInFrontend) |
| { |
| |
| ResetDevice(device, issueResetCommand: false); |
| device.disabledInFrontend = true; |
| } |
| break; |
|
|
| case DeviceDisableScope.InFrontendOnly: |
| device.disabledWhileInBackground = false; |
| if (!device.disabledInRuntime && device.disabledInFrontend) |
| return; |
| if (device.disabledInRuntime) |
| { |
| device.ExecuteEnableCommand(); |
| device.disabledInRuntime = false; |
| } |
| if (!device.disabledInFrontend) |
| { |
| |
| ResetDevice(device, issueResetCommand: false); |
| device.disabledInFrontend = true; |
| } |
| break; |
|
|
| case DeviceDisableScope.TemporaryWhilePlayerIsInBackground: |
| |
| |
| if (device.disabledInFrontend || device.disabledWhileInBackground) |
| return; |
| device.disabledWhileInBackground = true; |
| ResetDevice(device, issueResetCommand: false); |
| #if UNITY_EDITOR |
| if (m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView) |
| #endif |
| { |
| device.ExecuteDisableCommand(); |
| device.disabledInRuntime = true; |
| } |
| break; |
| } |
| } |
|
|
| |
| var deviceChange = enable ? InputDeviceChange.Enabled : InputDeviceChange.Disabled; |
| DelegateHelpers.InvokeCallbacksSafe(ref m_DeviceChangeListeners, device, deviceChange, "InputSystem.onDeviceChange"); |
| } |
|
|
| private unsafe void QueueEvent(InputEvent* eventPtr) |
| { |
| |
| |
| if (m_InputEventStream.isOpen) |
| { |
| m_InputEventStream.Write(eventPtr); |
| return; |
| } |
|
|
| |
| |
| m_Runtime.QueueEvent(eventPtr); |
| } |
|
|
| public unsafe void QueueEvent(InputEventPtr ptr) |
| { |
| QueueEvent(ptr.data); |
| } |
|
|
| public unsafe void QueueEvent<TEvent>(ref TEvent inputEvent) |
| where TEvent : struct, IInputEventTypeInfo |
| { |
| QueueEvent((InputEvent*)UnsafeUtility.AddressOf(ref inputEvent)); |
| } |
|
|
| public void Update() |
| { |
| Update(defaultUpdateType); |
| } |
|
|
| public void Update(InputUpdateType updateType) |
| { |
| m_Runtime.Update(updateType); |
| } |
|
|
| internal void Initialize(IInputRuntime runtime, InputSettings settings) |
| { |
| Debug.Assert(settings != null); |
|
|
| m_Settings = settings; |
|
|
| InitializeData(); |
| InstallRuntime(runtime); |
| InstallGlobals(); |
|
|
| ApplySettings(); |
| } |
|
|
| internal void Destroy() |
| { |
| |
| |
| |
| for (var i = 0; i < m_DevicesCount; ++i) |
| m_Devices[i].NotifyRemoved(); |
|
|
| |
| m_StateBuffers.FreeAll(); |
|
|
| |
| UninstallGlobals(); |
|
|
| |
| if (m_Settings != null && m_Settings.hideFlags == HideFlags.HideAndDontSave) |
| Object.DestroyImmediate(m_Settings); |
| } |
|
|
| internal void InitializeData() |
| { |
| m_Layouts.Allocate(); |
| m_Processors.Initialize(); |
| m_Interactions.Initialize(); |
| m_Composites.Initialize(); |
| m_DevicesById = new Dictionary<int, InputDevice>(); |
|
|
| |
| |
| |
| |
| m_UpdateMask = InputUpdateType.Dynamic | InputUpdateType.Fixed; |
| m_HasFocus = Application.isFocused; |
| #if UNITY_EDITOR |
| m_EditorIsActive = true; |
| m_UpdateMask |= InputUpdateType.Editor; |
| #endif |
|
|
| |
| m_PollingFrequency = 60; |
|
|
| |
| |
| |
| RegisterControlLayout("Axis", typeof(AxisControl)); |
| RegisterControlLayout("Button", typeof(ButtonControl)); |
| RegisterControlLayout("DiscreteButton", typeof(DiscreteButtonControl)); |
| RegisterControlLayout("Key", typeof(KeyControl)); |
| RegisterControlLayout("Analog", typeof(AxisControl)); |
| RegisterControlLayout("Integer", typeof(IntegerControl)); |
| RegisterControlLayout("Digital", typeof(IntegerControl)); |
| RegisterControlLayout("Double", typeof(DoubleControl)); |
| RegisterControlLayout("Vector2", typeof(Vector2Control)); |
| RegisterControlLayout("Vector3", typeof(Vector3Control)); |
| RegisterControlLayout("Delta", typeof(DeltaControl)); |
| RegisterControlLayout("Quaternion", typeof(QuaternionControl)); |
| RegisterControlLayout("Stick", typeof(StickControl)); |
| RegisterControlLayout("Dpad", typeof(DpadControl)); |
| RegisterControlLayout("DpadAxis", typeof(DpadControl.DpadAxisControl)); |
| RegisterControlLayout("AnyKey", typeof(AnyKeyControl)); |
| RegisterControlLayout("Touch", typeof(TouchControl)); |
| RegisterControlLayout("TouchPhase", typeof(TouchPhaseControl)); |
| RegisterControlLayout("TouchPress", typeof(TouchPressControl)); |
|
|
| RegisterControlLayout("Gamepad", typeof(Gamepad)); |
| RegisterControlLayout("Joystick", typeof(Joystick)); |
| RegisterControlLayout("Keyboard", typeof(Keyboard)); |
| RegisterControlLayout("Pointer", typeof(Pointer)); |
| RegisterControlLayout("Mouse", typeof(Mouse)); |
| RegisterControlLayout("Pen", typeof(Pen)); |
| RegisterControlLayout("Touchscreen", typeof(Touchscreen)); |
| RegisterControlLayout("Sensor", typeof(Sensor)); |
| RegisterControlLayout("Accelerometer", typeof(Accelerometer)); |
| RegisterControlLayout("Gyroscope", typeof(Gyroscope)); |
| RegisterControlLayout("GravitySensor", typeof(GravitySensor)); |
| RegisterControlLayout("AttitudeSensor", typeof(AttitudeSensor)); |
| RegisterControlLayout("LinearAccelerationSensor", typeof(LinearAccelerationSensor)); |
| RegisterControlLayout("MagneticFieldSensor", typeof(MagneticFieldSensor)); |
| RegisterControlLayout("LightSensor", typeof(LightSensor)); |
| RegisterControlLayout("PressureSensor", typeof(PressureSensor)); |
| RegisterControlLayout("HumiditySensor", typeof(HumiditySensor)); |
| RegisterControlLayout("AmbientTemperatureSensor", typeof(AmbientTemperatureSensor)); |
| RegisterControlLayout("StepCounter", typeof(StepCounter)); |
| RegisterControlLayout("TrackedDevice", typeof(TrackedDevice)); |
|
|
| |
| RegisterPrecompiledLayout<FastKeyboard>(FastKeyboard.metadata); |
| RegisterPrecompiledLayout<FastTouchscreen>(FastTouchscreen.metadata); |
| RegisterPrecompiledLayout<FastMouse>(FastMouse.metadata); |
|
|
| |
| processors.AddTypeRegistration("Invert", typeof(InvertProcessor)); |
| processors.AddTypeRegistration("InvertVector2", typeof(InvertVector2Processor)); |
| processors.AddTypeRegistration("InvertVector3", typeof(InvertVector3Processor)); |
| processors.AddTypeRegistration("Clamp", typeof(ClampProcessor)); |
| processors.AddTypeRegistration("Normalize", typeof(NormalizeProcessor)); |
| processors.AddTypeRegistration("NormalizeVector2", typeof(NormalizeVector2Processor)); |
| processors.AddTypeRegistration("NormalizeVector3", typeof(NormalizeVector3Processor)); |
| processors.AddTypeRegistration("Scale", typeof(ScaleProcessor)); |
| processors.AddTypeRegistration("ScaleVector2", typeof(ScaleVector2Processor)); |
| processors.AddTypeRegistration("ScaleVector3", typeof(ScaleVector3Processor)); |
| processors.AddTypeRegistration("StickDeadzone", typeof(StickDeadzoneProcessor)); |
| processors.AddTypeRegistration("AxisDeadzone", typeof(AxisDeadzoneProcessor)); |
| processors.AddTypeRegistration("CompensateDirection", typeof(CompensateDirectionProcessor)); |
| processors.AddTypeRegistration("CompensateRotation", typeof(CompensateRotationProcessor)); |
|
|
| #if UNITY_EDITOR |
| processors.AddTypeRegistration("AutoWindowSpace", typeof(EditorWindowSpaceProcessor)); |
| #endif |
|
|
| |
| interactions.AddTypeRegistration("Hold", typeof(HoldInteraction)); |
| interactions.AddTypeRegistration("Tap", typeof(TapInteraction)); |
| interactions.AddTypeRegistration("SlowTap", typeof(SlowTapInteraction)); |
| interactions.AddTypeRegistration("MultiTap", typeof(MultiTapInteraction)); |
| interactions.AddTypeRegistration("Press", typeof(PressInteraction)); |
|
|
| |
| composites.AddTypeRegistration("1DAxis", typeof(AxisComposite)); |
| composites.AddTypeRegistration("2DVector", typeof(Vector2Composite)); |
| composites.AddTypeRegistration("3DVector", typeof(Vector3Composite)); |
| composites.AddTypeRegistration("Axis", typeof(AxisComposite)); |
| composites.AddTypeRegistration("Dpad", typeof(Vector2Composite)); |
| composites.AddTypeRegistration("ButtonWithOneModifier", typeof(ButtonWithOneModifier)); |
| composites.AddTypeRegistration("ButtonWithTwoModifiers", typeof(ButtonWithTwoModifiers)); |
| composites.AddTypeRegistration("OneModifier", typeof(OneModifierComposite)); |
| composites.AddTypeRegistration("TwoModifiers", typeof(TwoModifiersComposite)); |
| } |
|
|
| internal void InstallRuntime(IInputRuntime runtime) |
| { |
| if (m_Runtime != null) |
| { |
| m_Runtime.onUpdate = null; |
| m_Runtime.onBeforeUpdate = null; |
| m_Runtime.onDeviceDiscovered = null; |
| m_Runtime.onPlayerFocusChanged = null; |
| m_Runtime.onShouldRunUpdate = null; |
| #if UNITY_EDITOR |
| m_Runtime.onPlayerLoopInitialization = null; |
| #endif |
| } |
|
|
| m_Runtime = runtime; |
| m_Runtime.onUpdate = OnUpdate; |
| m_Runtime.onDeviceDiscovered = OnNativeDeviceDiscovered; |
| m_Runtime.onPlayerFocusChanged = OnFocusChanged; |
| m_Runtime.onShouldRunUpdate = ShouldRunUpdate; |
| #if UNITY_EDITOR |
| m_Runtime.onPlayerLoopInitialization = OnPlayerLoopInitialization; |
| #endif |
| m_Runtime.pollingFrequency = pollingFrequency; |
| m_HasFocus = m_Runtime.isPlayerFocused; |
|
|
| |
| if (m_BeforeUpdateListeners.length > 0 || m_HaveDevicesWithStateCallbackReceivers) |
| { |
| m_Runtime.onBeforeUpdate = OnBeforeUpdate; |
| m_NativeBeforeUpdateHooked = true; |
| } |
|
|
| #if UNITY_ANALYTICS || UNITY_EDITOR |
| InputAnalytics.Initialize(this); |
| m_Runtime.onShutdown = () => InputAnalytics.OnShutdown(this); |
| #endif |
| } |
|
|
| internal void InstallGlobals() |
| { |
| Debug.Assert(m_Runtime != null); |
|
|
| InputControlLayout.s_Layouts = m_Layouts; |
| InputProcessor.s_Processors = m_Processors; |
| InputInteraction.s_Interactions = m_Interactions; |
| InputBindingComposite.s_Composites = m_Composites; |
|
|
| InputRuntime.s_Instance = m_Runtime; |
| InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup = |
| m_Runtime.currentTimeOffsetToRealtimeSinceStartup; |
|
|
| |
| InputUpdate.Restore(new InputUpdate.SerializedState()); |
|
|
| unsafe |
| { |
| InputStateBuffers.SwitchTo(m_StateBuffers, InputUpdateType.Dynamic); |
| InputStateBuffers.s_DefaultStateBuffer = m_StateBuffers.defaultStateBuffer; |
| InputStateBuffers.s_NoiseMaskBuffer = m_StateBuffers.noiseMaskBuffer; |
| InputStateBuffers.s_ResetMaskBuffer = m_StateBuffers.resetMaskBuffer; |
| } |
| } |
|
|
| internal void UninstallGlobals() |
| { |
| if (ReferenceEquals(InputControlLayout.s_Layouts.baseLayoutTable, m_Layouts.baseLayoutTable)) |
| InputControlLayout.s_Layouts = new InputControlLayout.Collection(); |
| if (ReferenceEquals(InputProcessor.s_Processors.table, m_Processors.table)) |
| InputProcessor.s_Processors = new TypeTable(); |
| if (ReferenceEquals(InputInteraction.s_Interactions.table, m_Interactions.table)) |
| InputInteraction.s_Interactions = new TypeTable(); |
| if (ReferenceEquals(InputBindingComposite.s_Composites.table, m_Composites.table)) |
| InputBindingComposite.s_Composites = new TypeTable(); |
|
|
| |
| InputControlLayout.s_CacheInstance = default; |
| InputControlLayout.s_CacheInstanceRef = 0; |
|
|
| |
| if (m_Runtime != null) |
| { |
| m_Runtime.onUpdate = null; |
| m_Runtime.onDeviceDiscovered = null; |
| m_Runtime.onBeforeUpdate = null; |
| m_Runtime.onPlayerFocusChanged = null; |
| m_Runtime.onShouldRunUpdate = null; |
|
|
| if (ReferenceEquals(InputRuntime.s_Instance, m_Runtime)) |
| InputRuntime.s_Instance = null; |
| } |
| } |
|
|
| [Serializable] |
| internal struct AvailableDevice |
| { |
| public InputDeviceDescription description; |
| public int deviceId; |
| public bool isNative; |
| public bool isRemoved; |
| } |
|
|
| |
| internal int m_LayoutRegistrationVersion; |
| private float m_PollingFrequency; |
|
|
| internal InputControlLayout.Collection m_Layouts; |
| private TypeTable m_Processors; |
| private TypeTable m_Interactions; |
| private TypeTable m_Composites; |
|
|
| private int m_DevicesCount; |
| private InputDevice[] m_Devices; |
|
|
| private Dictionary<int, InputDevice> m_DevicesById; |
| internal int m_AvailableDeviceCount; |
| internal AvailableDevice[] m_AvailableDevices; |
|
|
| |
| internal int m_DisconnectedDevicesCount; |
| internal InputDevice[] m_DisconnectedDevices; |
|
|
| internal InputUpdateType m_UpdateMask; |
| private InputUpdateType m_CurrentUpdate; |
| internal InputStateBuffers m_StateBuffers; |
|
|
| #if UNITY_EDITOR |
| |
| private double latestNonEditorTimeOffsetToRealtimeSinceStartup; |
| #endif |
|
|
| |
| |
| |
| private CallbackArray<DeviceChangeListener> m_DeviceChangeListeners; |
| private CallbackArray<DeviceStateChangeListener> m_DeviceStateChangeListeners; |
| private CallbackArray<InputDeviceFindControlLayoutDelegate> m_DeviceFindLayoutCallbacks; |
| internal CallbackArray<InputDeviceCommandDelegate> m_DeviceCommandCallbacks; |
| private CallbackArray<LayoutChangeListener> m_LayoutChangeListeners; |
| private CallbackArray<EventListener> m_EventListeners; |
| private CallbackArray<UpdateListener> m_BeforeUpdateListeners; |
| private CallbackArray<UpdateListener> m_AfterUpdateListeners; |
| private CallbackArray<Action> m_SettingsChangedListeners; |
| private bool m_NativeBeforeUpdateHooked; |
| private bool m_HaveDevicesWithStateCallbackReceivers; |
| private bool m_HasFocus; |
| private InputEventStream m_InputEventStream; |
|
|
| |
| |
| #if UNITY_EDITOR |
| private bool m_EditorIsActive; |
| #endif |
|
|
| |
| #if UNITY_EDITOR |
| private Utilities.CallbackArray<CustomBindingPathValidator> m_customBindingPathValidators; |
| #endif |
|
|
| |
| |
| private InputDeviceExecuteCommandDelegate m_DeviceFindExecuteCommandDelegate; |
| private int m_DeviceFindExecuteCommandDeviceId; |
|
|
| #if UNITY_ANALYTICS || UNITY_EDITOR |
| private bool m_HaveSentStartupAnalytics; |
| #endif |
|
|
| internal IInputRuntime m_Runtime; |
| internal InputMetrics m_Metrics; |
| internal InputSettings m_Settings; |
|
|
| #if UNITY_EDITOR |
| internal IInputDiagnostics m_Diagnostics; |
| #endif |
|
|
| |
|
|
| private void MakeDeviceNameUnique(InputDevice device) |
| { |
| if (m_DevicesCount == 0) |
| return; |
|
|
| var deviceName = StringHelpers.MakeUniqueName(device.name, m_Devices, x => x != null ? x.name : string.Empty); |
| if (deviceName != device.name) |
| { |
| |
| |
| ResetControlPathsRecursive(device); |
|
|
| |
| device.m_Name = new InternedString(deviceName); |
| } |
| } |
|
|
| private static void ResetControlPathsRecursive(InputControl control) |
| { |
| control.m_Path = null; |
|
|
| var children = control.children; |
| var childCount = children.Count; |
|
|
| for (var i = 0; i < childCount; ++i) |
| ResetControlPathsRecursive(children[i]); |
| } |
|
|
| private void AssignUniqueDeviceId(InputDevice device) |
| { |
| |
| if (device.deviceId != InputDevice.InvalidDeviceId) |
| { |
| |
| |
| |
| var existingDeviceWithId = TryGetDeviceById(device.deviceId); |
| if (existingDeviceWithId != null) |
| throw new InvalidOperationException( |
| $"Duplicate device ID {device.deviceId} detected for devices '{device.name}' and '{existingDeviceWithId.name}'"); |
| } |
| else |
| { |
| device.m_DeviceId = m_Runtime.AllocateDeviceId(); |
| } |
| } |
|
|
| |
| |
| |
| private unsafe void ReallocateStateBuffers() |
| { |
| var oldBuffers = m_StateBuffers; |
|
|
| |
| var newBuffers = new InputStateBuffers(); |
| newBuffers.AllocateAll(m_Devices, m_DevicesCount); |
|
|
| |
| newBuffers.MigrateAll(m_Devices, m_DevicesCount, oldBuffers); |
|
|
| |
| oldBuffers.FreeAll(); |
| m_StateBuffers = newBuffers; |
| InputStateBuffers.s_DefaultStateBuffer = newBuffers.defaultStateBuffer; |
| InputStateBuffers.s_NoiseMaskBuffer = newBuffers.noiseMaskBuffer; |
| InputStateBuffers.s_ResetMaskBuffer = newBuffers.resetMaskBuffer; |
|
|
| |
| InputStateBuffers.SwitchTo(m_StateBuffers, |
| InputUpdate.s_LatestUpdateType != InputUpdateType.None ? InputUpdate.s_LatestUpdateType : defaultUpdateType); |
|
|
| |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| private unsafe void InitializeDefaultState(InputDevice device) |
| { |
| |
| if (!device.hasControlsWithDefaultState) |
| return; |
|
|
| |
| var controls = device.allControls; |
| var controlCount = controls.Count; |
| var defaultStateBuffer = m_StateBuffers.defaultStateBuffer; |
| for (var n = 0; n < controlCount; ++n) |
| { |
| var control = controls[n]; |
| if (!control.hasDefaultState) |
| continue; |
|
|
| control.m_StateBlock.Write(defaultStateBuffer, control.m_DefaultState); |
| } |
|
|
| |
| var stateBlock = device.m_StateBlock; |
| var deviceIndex = device.m_DeviceIndex; |
| if (m_StateBuffers.m_PlayerStateBuffers.valid) |
| { |
| stateBlock.CopyToFrom(m_StateBuffers.m_PlayerStateBuffers.GetFrontBuffer(deviceIndex), defaultStateBuffer); |
| stateBlock.CopyToFrom(m_StateBuffers.m_PlayerStateBuffers.GetBackBuffer(deviceIndex), defaultStateBuffer); |
| } |
|
|
| #if UNITY_EDITOR |
| if (m_StateBuffers.m_EditorStateBuffers.valid) |
| { |
| stateBlock.CopyToFrom(m_StateBuffers.m_EditorStateBuffers.GetFrontBuffer(deviceIndex), defaultStateBuffer); |
| stateBlock.CopyToFrom(m_StateBuffers.m_EditorStateBuffers.GetBackBuffer(deviceIndex), defaultStateBuffer); |
| } |
| #endif |
| } |
|
|
| private unsafe void InitializeDeviceState(InputDevice device) |
| { |
| Debug.Assert(device != null, "Device must not be null"); |
| Debug.Assert(device.added, "Device must have been added"); |
| Debug.Assert(device.stateBlock.byteOffset != InputStateBlock.InvalidOffset, "Device state block offset is invalid"); |
| Debug.Assert(device.stateBlock.byteOffset + device.stateBlock.alignedSizeInBytes <= m_StateBuffers.sizePerBuffer, |
| "Device state block is not contained in state buffer"); |
|
|
| var controls = device.allControls; |
| var controlCount = controls.Count; |
| var resetMaskBuffer = m_StateBuffers.resetMaskBuffer; |
|
|
| var haveControlsWithDefaultState = device.hasControlsWithDefaultState; |
|
|
| |
| |
| |
| |
| |
| |
| var noiseMaskBuffer = m_StateBuffers.noiseMaskBuffer; |
|
|
| |
| |
| |
| |
| MemoryHelpers.SetBitsInBuffer(noiseMaskBuffer, (int)device.stateBlock.byteOffset, 0, (int)device.stateBlock.sizeInBits, false); |
| MemoryHelpers.SetBitsInBuffer(resetMaskBuffer, (int)device.stateBlock.byteOffset, 0, (int)device.stateBlock.sizeInBits, true); |
|
|
| |
| var defaultStateBuffer = m_StateBuffers.defaultStateBuffer; |
| for (var n = 0; n < controlCount; ++n) |
| { |
| var control = controls[n]; |
|
|
| |
| if (control.usesStateFromOtherControl) |
| continue; |
|
|
| if (!control.noisy || control.dontReset) |
| { |
| ref var stateBlock = ref control.m_StateBlock; |
|
|
| Debug.Assert(stateBlock.byteOffset != InputStateBlock.InvalidOffset, "Byte offset is invalid on control's state block"); |
| Debug.Assert(stateBlock.bitOffset != InputStateBlock.InvalidOffset, "Bit offset is invalid on control's state block"); |
| Debug.Assert(stateBlock.sizeInBits != InputStateBlock.InvalidOffset, "Size is invalid on control's state block"); |
| Debug.Assert(stateBlock.byteOffset >= device.stateBlock.byteOffset, "Control's offset is located below device's offset"); |
| Debug.Assert(stateBlock.byteOffset + stateBlock.alignedSizeInBytes <= |
| device.stateBlock.byteOffset + device.stateBlock.alignedSizeInBytes, "Control state block lies outside of state buffer"); |
|
|
| |
| if (!control.noisy) |
| MemoryHelpers.SetBitsInBuffer(noiseMaskBuffer, (int)stateBlock.byteOffset, (int)stateBlock.bitOffset, |
| (int)stateBlock.sizeInBits, true); |
|
|
| |
| if (control.dontReset) |
| MemoryHelpers.SetBitsInBuffer(resetMaskBuffer, (int)stateBlock.byteOffset, (int)stateBlock.bitOffset, |
| (int)stateBlock.sizeInBits, false); |
| } |
|
|
| |
| if (haveControlsWithDefaultState && control.hasDefaultState) |
| control.m_StateBlock.Write(defaultStateBuffer, control.m_DefaultState); |
| } |
|
|
| |
| if (haveControlsWithDefaultState) |
| { |
| ref var deviceStateBlock = ref device.m_StateBlock; |
| var deviceIndex = device.m_DeviceIndex; |
| if (m_StateBuffers.m_PlayerStateBuffers.valid) |
| { |
| deviceStateBlock.CopyToFrom(m_StateBuffers.m_PlayerStateBuffers.GetFrontBuffer(deviceIndex), defaultStateBuffer); |
| deviceStateBlock.CopyToFrom(m_StateBuffers.m_PlayerStateBuffers.GetBackBuffer(deviceIndex), defaultStateBuffer); |
| } |
|
|
| #if UNITY_EDITOR |
| if (m_StateBuffers.m_EditorStateBuffers.valid) |
| { |
| deviceStateBlock.CopyToFrom(m_StateBuffers.m_EditorStateBuffers.GetFrontBuffer(deviceIndex), defaultStateBuffer); |
| deviceStateBlock.CopyToFrom(m_StateBuffers.m_EditorStateBuffers.GetBackBuffer(deviceIndex), defaultStateBuffer); |
| } |
| #endif |
| } |
| } |
|
|
| private void OnNativeDeviceDiscovered(int deviceId, string deviceDescriptor) |
| { |
| |
| |
| RestoreDevicesAfterDomainReloadIfNecessary(); |
|
|
| |
| |
| |
| var device = TryMatchDisconnectedDevice(deviceDescriptor); |
|
|
| |
| var description = device?.description ?? InputDeviceDescription.FromJson(deviceDescriptor); |
|
|
| |
| var markAsRemoved = false; |
| try |
| { |
| |
| |
| if (m_Settings.supportedDevices.Count > 0) |
| { |
| var layout = device != null ? device.m_Layout : TryFindMatchingControlLayout(ref description, deviceId); |
| if (!IsDeviceLayoutMarkedAsSupportedInSettings(layout)) |
| { |
| |
| |
| |
| |
| markAsRemoved = true; |
| return; |
| } |
| } |
|
|
| if (device != null) |
| { |
| |
| |
|
|
| device.m_DeviceId = deviceId; |
| device.m_DeviceFlags |= InputDevice.DeviceFlags.Native; |
| device.m_DeviceFlags &= ~InputDevice.DeviceFlags.DisabledInFrontend; |
| device.m_DeviceFlags &= ~InputDevice.DeviceFlags.DisabledWhileInBackground; |
| device.m_DeviceFlags &= ~InputDevice.DeviceFlags.DisabledStateHasBeenQueriedFromRuntime; |
|
|
| AddDevice(device); |
|
|
| DelegateHelpers.InvokeCallbacksSafe(ref m_DeviceChangeListeners, device, InputDeviceChange.Reconnected, |
| "InputSystem.onDeviceChange"); |
| } |
| else |
| { |
| |
| AddDevice(description, throwIfNoLayoutFound: false, deviceId: deviceId, |
| deviceFlags: InputDevice.DeviceFlags.Native); |
| } |
| } |
| |
| |
| |
| |
| catch (Exception exception) |
| { |
| Debug.LogError($"Could not create a device for '{description}' (exception: {exception})"); |
| } |
| finally |
| { |
| |
| |
| |
| |
| ArrayHelpers.AppendWithCapacity(ref m_AvailableDevices, ref m_AvailableDeviceCount, |
| new AvailableDevice |
| { |
| description = description, |
| deviceId = deviceId, |
| isNative = true, |
| isRemoved = markAsRemoved, |
| }); |
| } |
| } |
|
|
| private InputDevice TryMatchDisconnectedDevice(string deviceDescriptor) |
| { |
| for (var i = 0; i < m_DisconnectedDevicesCount; ++i) |
| { |
| var device = m_DisconnectedDevices[i]; |
| var description = device.description; |
|
|
| |
| |
|
|
| if (!InputDeviceDescription.ComparePropertyToDeviceDescriptor("interface", description.interfaceName, deviceDescriptor)) |
| continue; |
| if (!InputDeviceDescription.ComparePropertyToDeviceDescriptor("product", description.product, deviceDescriptor)) |
| continue; |
| if (!InputDeviceDescription.ComparePropertyToDeviceDescriptor("manufacturer", description.manufacturer, deviceDescriptor)) |
| continue; |
| if (!InputDeviceDescription.ComparePropertyToDeviceDescriptor("type", description.deviceClass, deviceDescriptor)) |
| continue; |
| if (!InputDeviceDescription.ComparePropertyToDeviceDescriptor("capabilities", description.capabilities, deviceDescriptor)) |
| continue; |
| if (!InputDeviceDescription.ComparePropertyToDeviceDescriptor("serial", description.serial, deviceDescriptor)) |
| continue; |
|
|
| ArrayHelpers.EraseAtWithCapacity(m_DisconnectedDevices, ref m_DisconnectedDevicesCount, i); |
| return device; |
| } |
|
|
| return null; |
| } |
|
|
| private void InstallBeforeUpdateHookIfNecessary() |
| { |
| if (m_NativeBeforeUpdateHooked || m_Runtime == null) |
| return; |
|
|
| m_Runtime.onBeforeUpdate = OnBeforeUpdate; |
| m_NativeBeforeUpdateHooked = true; |
| } |
|
|
| private void RestoreDevicesAfterDomainReloadIfNecessary() |
| { |
| #if UNITY_EDITOR |
| if (m_SavedDeviceStates != null) |
| RestoreDevicesAfterDomainReload(); |
| #endif |
| } |
|
|
| #if UNITY_EDITOR |
| private void SyncAllDevicesWhenEditorIsActivated() |
| { |
| var isActive = m_Runtime.isEditorActive; |
| if (isActive == m_EditorIsActive) |
| return; |
|
|
| m_EditorIsActive = isActive; |
| if (m_EditorIsActive) |
| SyncAllDevices(); |
| } |
|
|
| private void SyncAllDevices() |
| { |
| for (var i = 0; i < m_DevicesCount; ++i) |
| { |
| |
| |
| |
| if (!m_Devices[i].RequestSync()) |
| ResetDevice(m_Devices[i], issueResetCommand: true); |
| } |
| } |
|
|
| internal void SyncAllDevicesAfterEnteringPlayMode() |
| { |
| |
| |
| |
| |
| SyncAllDevices(); |
| } |
|
|
| #endif |
|
|
| private void WarnAboutDevicesFailingToRecreateAfterDomainReload() |
| { |
| |
| |
| |
| #if UNITY_EDITOR |
| if (m_SavedDeviceStates == null) |
| return; |
|
|
| for (var i = 0; i < m_SavedDeviceStates.Length; ++i) |
| { |
| ref var state = ref m_SavedDeviceStates[i]; |
| Debug.LogWarning($"Could not recreate device '{state.name}' with layout '{state.layout}' after domain reload"); |
| } |
|
|
| |
| |
| m_SavedDeviceStates = null; |
| #endif |
| } |
|
|
| private void OnBeforeUpdate(InputUpdateType updateType) |
| { |
| |
| RestoreDevicesAfterDomainReloadIfNecessary(); |
|
|
| if ((updateType & m_UpdateMask) == 0) |
| return; |
|
|
| InputStateBuffers.SwitchTo(m_StateBuffers, updateType); |
|
|
| InputUpdate.OnBeforeUpdate(updateType); |
|
|
| |
| |
| if (m_HaveDevicesWithStateCallbackReceivers && updateType != InputUpdateType.BeforeRender) |
| { |
| for (var i = 0; i < m_DevicesCount; ++i) |
| { |
| var device = m_Devices[i]; |
| if (!device.hasStateCallbacks) |
| continue; |
|
|
| |
| |
| |
| |
| |
| |
|
|
| ((IInputStateCallbackReceiver)device).OnNextUpdate(); |
| } |
| } |
|
|
| DelegateHelpers.InvokeCallbacksSafe(ref m_BeforeUpdateListeners, "onBeforeUpdate"); |
| } |
|
|
| |
| |
| |
| internal void ApplySettings() |
| { |
| |
| var newUpdateMask = InputUpdateType.Editor; |
| if ((m_UpdateMask & InputUpdateType.BeforeRender) != 0) |
| { |
| |
| |
| newUpdateMask |= InputUpdateType.BeforeRender; |
| } |
| if (m_Settings.updateMode == InputSettings.s_OldUnsupportedFixedAndDynamicUpdateSetting) |
| m_Settings.updateMode = InputSettings.UpdateMode.ProcessEventsInDynamicUpdate; |
| switch (m_Settings.updateMode) |
| { |
| case InputSettings.UpdateMode.ProcessEventsInDynamicUpdate: |
| newUpdateMask |= InputUpdateType.Dynamic; |
| break; |
| case InputSettings.UpdateMode.ProcessEventsInFixedUpdate: |
| newUpdateMask |= InputUpdateType.Fixed; |
| break; |
| case InputSettings.UpdateMode.ProcessEventsManually: |
| newUpdateMask |= InputUpdateType.Manual; |
| break; |
| default: |
| throw new NotSupportedException("Invalid input update mode: " + m_Settings.updateMode); |
| } |
|
|
| #if UNITY_EDITOR |
| |
| |
| |
| newUpdateMask |= InputUpdateType.Editor; |
| #endif |
| updateMask = newUpdateMask; |
|
|
| |
| |
|
|
| |
| |
| AddAvailableDevicesThatAreNowRecognized(); |
|
|
| |
| |
| if (settings.supportedDevices.Count > 0) |
| { |
| for (var i = 0; i < m_DevicesCount; ++i) |
| { |
| var device = m_Devices[i]; |
| var layout = device.m_Layout; |
|
|
| |
| |
| |
| var isInAvailableDevices = false; |
| for (var n = 0; n < m_AvailableDeviceCount; ++n) |
| { |
| if (m_AvailableDevices[n].deviceId == device.deviceId) |
| { |
| isInAvailableDevices = true; |
| break; |
| } |
| } |
| if (!isInAvailableDevices) |
| continue; |
|
|
| |
| |
| if (!IsDeviceLayoutMarkedAsSupportedInSettings(layout)) |
| { |
| RemoveDevice(device, keepOnListOfAvailableDevices: true); |
| --i; |
| } |
| } |
| } |
|
|
| |
| if (m_Settings.m_FeatureFlags != null) |
| { |
| #if UNITY_EDITOR |
| runPlayerUpdatesInEditMode = m_Settings.IsFeatureEnabled(InputFeatureNames.kRunPlayerUpdatesInEditMode); |
| #endif |
|
|
| if (m_Settings.IsFeatureEnabled(InputFeatureNames.kUseWindowsGamingInputBackend)) |
| { |
| var command = UseWindowsGamingInputCommand.Create(true); |
| if (ExecuteGlobalCommand(ref command) < 0) |
| Debug.LogError($"Could not enable Windows.Gaming.Input"); |
| } |
| } |
|
|
| |
| Touchscreen.s_TapTime = settings.defaultTapTime; |
| Touchscreen.s_TapDelayTime = settings.multiTapDelayTime; |
| Touchscreen.s_TapRadiusSquared = settings.tapRadius * settings.tapRadius; |
| |
| ButtonControl.s_GlobalDefaultButtonPressPoint = Mathf.Clamp(settings.defaultButtonPressPoint, ButtonControl.kMinButtonPressPoint, float.MaxValue); |
| ButtonControl.s_GlobalDefaultButtonReleaseThreshold = settings.buttonReleaseThreshold; |
|
|
| |
| foreach (var device in devices) |
| device.SetOptimizedControlDataTypeRecursively(); |
|
|
| |
| foreach (var device in devices) |
| device.MarkAsStaleRecursively(); |
|
|
| |
| DelegateHelpers.InvokeCallbacksSafe(ref m_SettingsChangedListeners, |
| "InputSystem.onSettingsChange"); |
| } |
|
|
| internal unsafe long ExecuteGlobalCommand<TCommand>(ref TCommand command) |
| where TCommand : struct, IInputDeviceCommandInfo |
| { |
| var ptr = (InputDeviceCommand*)UnsafeUtility.AddressOf(ref command); |
| |
| return InputRuntime.s_Instance.DeviceCommand(0, ptr); |
| } |
|
|
| internal void AddAvailableDevicesThatAreNowRecognized() |
| { |
| for (var i = 0; i < m_AvailableDeviceCount; ++i) |
| { |
| var id = m_AvailableDevices[i].deviceId; |
| if (TryGetDeviceById(id) != null) |
| continue; |
|
|
| var layout = TryFindMatchingControlLayout(ref m_AvailableDevices[i].description, id); |
| if (!IsDeviceLayoutMarkedAsSupportedInSettings(layout)) continue; |
|
|
| if (layout.IsEmpty()) |
| { |
| |
| if (id != InputDevice.InvalidDeviceId) |
| { |
| var command = DisableDeviceCommand.Create(); |
| m_Runtime.DeviceCommand(id, ref command); |
| } |
|
|
| continue; |
| } |
|
|
| try |
| { |
| AddDevice(m_AvailableDevices[i].description, layout, deviceId: id, |
| deviceFlags: m_AvailableDevices[i].isNative ? InputDevice.DeviceFlags.Native : 0); |
| } |
| catch (Exception) |
| { |
| |
| |
| |
| } |
| } |
| } |
|
|
| private bool ShouldRunDeviceInBackground(InputDevice device) |
| { |
| var runDeviceInBackground = |
| m_Settings.backgroundBehavior != InputSettings.BackgroundBehavior.ResetAndDisableAllDevices && |
| device.canRunInBackground; |
|
|
| |
| #if UNITY_EDITOR |
| if (runDeviceInBackground) |
| { |
| if (m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode.AllDevicesRespectGameViewFocus) |
| runDeviceInBackground = false; |
| else if (m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode.PointersAndKeyboardsRespectGameViewFocus) |
| runDeviceInBackground = !(device is Pointer || device is Keyboard); |
| } |
| #endif |
|
|
| return runDeviceInBackground; |
| } |
|
|
| internal void OnFocusChanged(bool focus) |
| { |
| #if UNITY_EDITOR |
| SyncAllDevicesWhenEditorIsActivated(); |
|
|
| if (!m_Runtime.isInPlayMode) |
| { |
| m_HasFocus = focus; |
| return; |
| } |
| #endif |
|
|
| #if UNITY_EDITOR |
| var gameViewFocus = m_Settings.editorInputBehaviorInPlayMode; |
| #endif |
|
|
| var runInBackground = |
| #if UNITY_EDITOR |
| |
| |
| |
| |
| |
| |
| gameViewFocus != InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView || m_Runtime.runInBackground; |
| #else |
| m_Runtime.runInBackground; |
| #endif |
|
|
| var backgroundBehavior = m_Settings.backgroundBehavior; |
| if (backgroundBehavior == InputSettings.BackgroundBehavior.IgnoreFocus && runInBackground) |
| { |
| |
| |
| m_HasFocus = focus; |
| return; |
| } |
|
|
| #if UNITY_EDITOR |
| |
| |
| |
| m_CurrentUpdate = m_UpdateMask.GetUpdateTypeForPlayer(); |
| #endif |
|
|
| if (!focus) |
| { |
| |
| |
| if (runInBackground) |
| { |
| for (var i = 0; i < m_DevicesCount; ++i) |
| { |
| |
| var device = m_Devices[i]; |
| if (!device.enabled || ShouldRunDeviceInBackground(device)) |
| continue; |
|
|
| |
| EnableOrDisableDevice(device, false, DeviceDisableScope.TemporaryWhilePlayerIsInBackground); |
|
|
| |
| var index = m_Devices.IndexOfReference(device, m_DevicesCount); |
| if (index == -1) |
| --i; |
| else |
| i = index; |
| } |
| } |
| } |
| else |
| { |
| |
| for (var i = 0; i < m_DevicesCount; ++i) |
| { |
| var device = m_Devices[i]; |
|
|
| |
| if (device.disabledWhileInBackground) |
| EnableOrDisableDevice(device, true, DeviceDisableScope.TemporaryWhilePlayerIsInBackground); |
| |
| |
| |
| |
| |
| else if (device.enabled && !runInBackground && !device.RequestSync()) |
| ResetDevice(device); |
| } |
| } |
|
|
| #if UNITY_EDITOR |
| m_CurrentUpdate = InputUpdateType.None; |
| #endif |
|
|
| |
| m_HasFocus = focus; |
| } |
|
|
| #if UNITY_EDITOR |
| internal void LeavePlayMode() |
| { |
| |
| m_CurrentUpdate = InputUpdate.GetUpdateTypeForPlayer(m_UpdateMask); |
| InputStateBuffers.SwitchTo(m_StateBuffers, m_CurrentUpdate); |
| for (var i = 0; i < m_DevicesCount; ++i) |
| { |
| var device = m_Devices[i]; |
| if (device.disabledWhileInBackground) |
| EnableOrDisableDevice(device, true, scope: DeviceDisableScope.TemporaryWhilePlayerIsInBackground); |
| ResetDevice(device, alsoResetDontResetControls: true); |
| } |
| m_CurrentUpdate = default; |
| } |
|
|
| private void OnPlayerLoopInitialization() |
| { |
| if (!gameIsPlaying || |
| !InputUpdate.s_LatestUpdateType.IsEditorUpdate() || |
| !InputUpdate.s_LatestNonEditorUpdateType.IsPlayerUpdate()) |
| return; |
|
|
| InputUpdate.RestoreStateAfterEditorUpdate(); |
| InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup = latestNonEditorTimeOffsetToRealtimeSinceStartup; |
| InputStateBuffers.SwitchTo(m_StateBuffers, InputUpdate.s_LatestUpdateType); |
| } |
|
|
| #endif |
|
|
| internal bool ShouldRunUpdate(InputUpdateType updateType) |
| { |
| |
| |
| if (updateType == InputUpdateType.None) |
| return true; |
|
|
| var mask = m_UpdateMask; |
|
|
| #if UNITY_EDITOR |
| |
| |
| |
| |
| |
| |
| if (!gameIsPlaying && updateType != InputUpdateType.Editor && !runPlayerUpdatesInEditMode) |
| return false; |
| #endif |
|
|
| return (updateType & mask) != 0; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1809:AvoidExcessiveLocals", Justification = "TODO: Refactor later.")] |
| private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer eventBuffer) |
| { |
| |
| |
| |
| Profiler.BeginSample("InputUpdate"); |
|
|
| if (m_InputEventStream.isOpen) |
| throw new InvalidOperationException("Already have an event buffer set! Was OnUpdate() called recursively?"); |
|
|
| |
| RestoreDevicesAfterDomainReloadIfNecessary(); |
|
|
| |
| #if UNITY_EDITOR |
| SyncAllDevicesWhenEditorIsActivated(); |
| #endif |
|
|
| if ((updateType & m_UpdateMask) == 0) |
| { |
| Profiler.EndSample(); |
| return; |
| } |
|
|
| WarnAboutDevicesFailingToRecreateAfterDomainReload(); |
|
|
| |
| #if UNITY_ANALYTICS || UNITY_EDITOR |
| if (!m_HaveSentStartupAnalytics) |
| { |
| InputAnalytics.OnStartup(this); |
| m_HaveSentStartupAnalytics = true; |
| } |
| #endif |
|
|
| |
| ++m_Metrics.totalUpdateCount; |
|
|
| #if UNITY_EDITOR |
| |
| |
| if (((updateType & InputUpdateType.Editor) == InputUpdateType.Editor) && (m_CurrentUpdate & InputUpdateType.Editor) == 0) |
| latestNonEditorTimeOffsetToRealtimeSinceStartup = |
| InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup; |
| #endif |
|
|
| |
| InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup = m_Runtime.currentTimeOffsetToRealtimeSinceStartup; |
|
|
| InputStateBuffers.SwitchTo(m_StateBuffers, updateType); |
|
|
| m_CurrentUpdate = updateType; |
| InputUpdate.OnUpdate(updateType); |
|
|
| |
| foreach (var device in devices) |
| device.EnsureOptimizationTypeHasNotChanged(); |
|
|
| var shouldProcessActionTimeouts = updateType.IsPlayerUpdate() && gameIsPlaying; |
|
|
| |
| |
| |
| |
| |
| |
|
|
| var currentTime = updateType == InputUpdateType.Fixed ? m_Runtime.currentTimeForFixedUpdate : m_Runtime.currentTime; |
| var timesliceEvents = (updateType == InputUpdateType.Fixed || updateType == InputUpdateType.BeforeRender) && |
| InputSystem.settings.updateMode == InputSettings.UpdateMode.ProcessEventsInFixedUpdate; |
|
|
| |
| var canFlushBuffer = |
| false |
| #if UNITY_EDITOR |
| |
| || (!gameHasFocus && m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView && |
| (!m_Runtime.runInBackground || |
| m_Settings.backgroundBehavior == InputSettings.BackgroundBehavior.ResetAndDisableAllDevices)) |
| #else |
| || (!gameHasFocus && !m_Runtime.runInBackground) |
| #endif |
| ; |
| var canEarlyOut = |
| |
| eventBuffer.eventCount == 0 |
| || canFlushBuffer || |
| |
| |
| ((!gameHasFocus || gameShouldGetInputRegardlessOfFocus) && |
| ((m_Settings.backgroundBehavior == InputSettings.BackgroundBehavior.ResetAndDisableAllDevices && updateType != InputUpdateType.Editor) |
| #if UNITY_EDITOR |
| || (m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode.AllDevicesRespectGameViewFocus && updateType != InputUpdateType.Editor) |
| || (m_Settings.backgroundBehavior == InputSettings.BackgroundBehavior.IgnoreFocus && m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView && updateType == InputUpdateType.Editor) |
| #endif |
| ) |
| #if UNITY_EDITOR |
| |
| |
| || (gameIsPlaying && gameHasFocus && updateType == InputUpdateType.Editor) |
| #endif |
| ); |
|
|
|
|
| bool dropStatusEvents = false; |
|
|
| #if UNITY_EDITOR |
| if (!gameIsPlaying && gameShouldGetInputRegardlessOfFocus && (eventBuffer.sizeInBytes > (100 * 1024))) |
| { |
| |
| |
| canEarlyOut = false; |
| dropStatusEvents = true; |
| } |
| #endif |
|
|
| if (canEarlyOut) |
| { |
| |
| |
| if (shouldProcessActionTimeouts) |
| ProcessStateChangeMonitorTimeouts(); |
|
|
| Profiler.EndSample(); |
| InvokeAfterUpdateCallback(updateType); |
| if (canFlushBuffer) |
| eventBuffer.Reset(); |
| m_CurrentUpdate = default; |
| return; |
| } |
|
|
| var processingStartTime = Stopwatch.GetTimestamp(); |
| var totalEventLag = 0.0; |
|
|
| #if UNITY_EDITOR |
| var isPlaying = gameIsPlaying; |
| #endif |
|
|
| try |
| { |
| m_InputEventStream = new InputEventStream(ref eventBuffer, m_Settings.maxQueuedEventsPerUpdate); |
| var totalEventBytesProcessed = 0U; |
|
|
| InputEvent* skipEventMergingFor = null; |
|
|
| |
| while (m_InputEventStream.remainingEventCount > 0) |
| { |
| if (m_Settings.maxEventBytesPerUpdate > 0 && |
| totalEventBytesProcessed >= m_Settings.maxEventBytesPerUpdate) |
| { |
| Debug.LogError( |
| "Exceeded budget for maximum input event throughput per InputSystem.Update(). Discarding remaining events. " |
| + "Increase InputSystem.settings.maxEventBytesPerUpdate or set it to 0 to remove the limit."); |
| break; |
| } |
|
|
| InputDevice device = null; |
| var currentEventReadPtr = m_InputEventStream.currentEventPtr; |
|
|
| Debug.Assert(!currentEventReadPtr->handled, "Event in buffer is already marked as handled"); |
|
|
| |
| |
| if (updateType == InputUpdateType.BeforeRender) |
| { |
| while (m_InputEventStream.remainingEventCount > 0) |
| { |
| Debug.Assert(!currentEventReadPtr->handled, |
| "Iterated to event in buffer that is already marked as handled"); |
|
|
| device = TryGetDeviceById(currentEventReadPtr->deviceId); |
| if (device != null && device.updateBeforeRender && |
| (currentEventReadPtr->type == StateEvent.Type || |
| currentEventReadPtr->type == DeltaStateEvent.Type)) |
| break; |
|
|
| currentEventReadPtr = m_InputEventStream.Advance(leaveEventInBuffer: true); |
| } |
| } |
|
|
| if (m_InputEventStream.remainingEventCount == 0) |
| break; |
|
|
| var currentEventTimeInternal = currentEventReadPtr->internalTime; |
| var currentEventType = currentEventReadPtr->type; |
|
|
| #if UNITY_EDITOR |
| if (dropStatusEvents) |
| { |
| |
| if (currentEventType == StateEvent.Type || currentEventType == DeltaStateEvent.Type || currentEventType == IMECompositionEvent.Type) |
| m_InputEventStream.Advance(false); |
| else |
| m_InputEventStream.Advance(true); |
|
|
| continue; |
| } |
| #endif |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #if UNITY_EDITOR |
| if ((currentEventType == StateEvent.Type || |
| currentEventType == DeltaStateEvent.Type) && |
| (updateType & InputUpdateType.Editor) == 0 && |
| InputSystem.s_SystemObject.exitEditModeTime > 0 && |
| currentEventTimeInternal >= InputSystem.s_SystemObject.exitEditModeTime && |
| (currentEventTimeInternal < InputSystem.s_SystemObject.enterPlayModeTime || |
| InputSystem.s_SystemObject.enterPlayModeTime == 0)) |
| { |
| m_InputEventStream.Advance(false); |
| continue; |
| } |
| #endif |
|
|
| |
| if (timesliceEvents && currentEventTimeInternal >= currentTime) |
| { |
| m_InputEventStream.Advance(true); |
| continue; |
| } |
|
|
| |
| if (device == null) |
| device = TryGetDeviceById(currentEventReadPtr->deviceId); |
| if (device == null) |
| { |
| #if UNITY_EDITOR |
| |
| m_Diagnostics?.OnCannotFindDeviceForEvent(new InputEventPtr(currentEventReadPtr)); |
| #endif |
|
|
| m_InputEventStream.Advance(false); |
| continue; |
| } |
|
|
| |
| |
| #if UNITY_EDITOR |
| if (isPlaying && !gameHasFocus) |
| { |
| if (m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode |
| .PointersAndKeyboardsRespectGameViewFocus && |
| m_Settings.backgroundBehavior != |
| InputSettings.BackgroundBehavior.ResetAndDisableAllDevices) |
| { |
| var isPointerOrKeyboard = device is Pointer || device is Keyboard; |
| if (updateType != InputUpdateType.Editor) |
| { |
| |
| |
| |
| if (isPointerOrKeyboard) |
| { |
| m_InputEventStream.Advance(true); |
| continue; |
| } |
| } |
| else |
| { |
| |
| if (!isPointerOrKeyboard) |
| { |
| m_InputEventStream.Advance(true); |
| continue; |
| } |
| } |
| } |
| } |
| #endif |
|
|
| |
| |
| if (!device.enabled && |
| currentEventType != DeviceRemoveEvent.Type && |
| currentEventType != DeviceConfigurationEvent.Type && |
| (device.m_DeviceFlags & (InputDevice.DeviceFlags.DisabledInRuntime | |
| InputDevice.DeviceFlags.DisabledWhileInBackground)) != 0) |
| { |
| #if UNITY_EDITOR |
| |
| |
| if ((device.m_DeviceFlags & InputDevice.DeviceFlags.DisabledInRuntime) != 0) |
| m_Diagnostics?.OnEventForDisabledDevice(currentEventReadPtr, device); |
| #endif |
|
|
| m_InputEventStream.Advance(false); |
| continue; |
| } |
|
|
| |
| if (!settings.disableRedundantEventsMerging && device.hasEventMerger && currentEventReadPtr != skipEventMergingFor) |
| { |
| |
| |
| |
| |
|
|
| var nextEvent = m_InputEventStream.Peek(); |
| |
| if ((nextEvent != null) |
| |
| && (currentEventReadPtr->deviceId == nextEvent->deviceId) |
| |
| && (timesliceEvents ? (nextEvent->internalTime < currentTime) : true) |
| ) |
| { |
| |
| if (((IEventMerger)device).MergeForward(currentEventReadPtr, nextEvent)) |
| { |
| |
| m_InputEventStream.Advance(false); |
| continue; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| skipEventMergingFor = nextEvent; |
| } |
| } |
|
|
| |
| if (device.hasEventPreProcessor) |
| { |
| #if UNITY_EDITOR |
| var eventSizeBeforePreProcessor = currentEventReadPtr->sizeInBytes; |
| #endif |
| var shouldProcess = ((IEventPreProcessor)device).PreProcessEvent(currentEventReadPtr); |
| #if UNITY_EDITOR |
| if (currentEventReadPtr->sizeInBytes > eventSizeBeforePreProcessor) |
| throw new AccessViolationException($"'{device}'.PreProcessEvent tries to grow an event from {eventSizeBeforePreProcessor} bytes to {currentEventReadPtr->sizeInBytes} bytes, this will potentially corrupt events after the current event and/or cause out-of-bounds memory access."); |
| #endif |
| if (!shouldProcess) |
| { |
| |
| m_InputEventStream.Advance(false); |
| continue; |
| } |
| } |
|
|
| |
| |
| |
| |
| if (m_EventListeners.length > 0) |
| { |
| DelegateHelpers.InvokeCallbacksSafe(ref m_EventListeners, |
| new InputEventPtr(currentEventReadPtr), device, "InputSystem.onEvent"); |
|
|
| |
| if (currentEventReadPtr->handled) |
| { |
| m_InputEventStream.Advance(false); |
| continue; |
| } |
| } |
|
|
| |
| if (currentEventTimeInternal <= currentTime) |
| totalEventLag += currentTime - currentEventTimeInternal; |
| ++m_Metrics.totalEventCount; |
| m_Metrics.totalEventBytes += (int)currentEventReadPtr->sizeInBytes; |
|
|
| |
| switch (currentEventType) |
| { |
| case StateEvent.Type: |
| case DeltaStateEvent.Type: |
|
|
| var eventPtr = new InputEventPtr(currentEventReadPtr); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| var deviceIsStateCallbackReceiver = device.hasStateCallbacks; |
| if (currentEventTimeInternal < device.m_LastUpdateTimeInternal && |
| !(deviceIsStateCallbackReceiver && device.stateBlock.format != eventPtr.stateFormat)) |
| { |
| #if UNITY_EDITOR |
| m_Diagnostics?.OnEventTimestampOutdated(new InputEventPtr(currentEventReadPtr), device); |
| #endif |
| break; |
| } |
|
|
| |
| |
| var haveChangedStateOtherThanNoise = true; |
| if (deviceIsStateCallbackReceiver) |
| { |
| m_ShouldMakeCurrentlyUpdatingDeviceCurrent = true; |
| |
| |
| ((IInputStateCallbackReceiver)device).OnStateEvent(eventPtr); |
|
|
| haveChangedStateOtherThanNoise = m_ShouldMakeCurrentlyUpdatingDeviceCurrent; |
| } |
| else |
| { |
| |
| if (device.stateBlock.format != eventPtr.stateFormat) |
| { |
| #if UNITY_EDITOR |
| m_Diagnostics?.OnEventFormatMismatch(currentEventReadPtr, device); |
| #endif |
| break; |
| } |
|
|
| haveChangedStateOtherThanNoise = UpdateState(device, eventPtr, updateType); |
| } |
|
|
| totalEventBytesProcessed += eventPtr.sizeInBytes; |
|
|
| |
| |
| |
| |
| if (device.m_LastUpdateTimeInternal <= eventPtr.internalTime |
| #if UNITY_EDITOR |
| && !(updateType == InputUpdateType.Editor && runPlayerUpdatesInEditMode) |
| #endif |
| ) |
| device.m_LastUpdateTimeInternal = eventPtr.internalTime; |
|
|
| |
| if (haveChangedStateOtherThanNoise) |
| device.MakeCurrent(); |
|
|
| break; |
|
|
| case TextEvent.Type: |
| { |
| var textEventPtr = (TextEvent*)currentEventReadPtr; |
| if (device is ITextInputReceiver textInputReceiver) |
| { |
| var utf32Char = textEventPtr->character; |
| if (utf32Char >= 0x10000) |
| { |
| |
| utf32Char -= 0x10000; |
| var highSurrogate = 0xD800 + ((utf32Char >> 10) & 0x3FF); |
| var lowSurrogate = 0xDC00 + (utf32Char & 0x3FF); |
|
|
| textInputReceiver.OnTextInput((char)highSurrogate); |
| textInputReceiver.OnTextInput((char)lowSurrogate); |
| } |
| else |
| { |
| |
| textInputReceiver.OnTextInput((char)utf32Char); |
| } |
| } |
|
|
| break; |
| } |
|
|
| case IMECompositionEvent.Type: |
| { |
| var imeEventPtr = (IMECompositionEvent*)currentEventReadPtr; |
| var textInputReceiver = device as ITextInputReceiver; |
| textInputReceiver?.OnIMECompositionChanged(imeEventPtr->compositionString); |
| break; |
| } |
|
|
| case DeviceRemoveEvent.Type: |
| { |
| RemoveDevice(device, keepOnListOfAvailableDevices: false); |
|
|
| |
| |
| if (device.native && !device.description.empty) |
| { |
| ArrayHelpers.AppendWithCapacity(ref m_DisconnectedDevices, |
| ref m_DisconnectedDevicesCount, device); |
| DelegateHelpers.InvokeCallbacksSafe(ref m_DeviceChangeListeners, |
| device, InputDeviceChange.Disconnected, "InputSystem.onDeviceChange"); |
| } |
|
|
| break; |
| } |
|
|
| case DeviceConfigurationEvent.Type: |
| device.NotifyConfigurationChanged(); |
| InputActionState.OnDeviceChange(device, InputDeviceChange.ConfigurationChanged); |
| DelegateHelpers.InvokeCallbacksSafe(ref m_DeviceChangeListeners, |
| device, InputDeviceChange.ConfigurationChanged, "InputSystem.onDeviceChange"); |
| break; |
|
|
| case DeviceResetEvent.Type: |
| ResetDevice(device, |
| alsoResetDontResetControls: ((DeviceResetEvent*)currentEventReadPtr)->hardReset); |
| break; |
| } |
|
|
| m_InputEventStream.Advance(leaveEventInBuffer: false); |
| } |
|
|
| m_Metrics.totalEventProcessingTime += |
| ((double)(Stopwatch.GetTimestamp() - processingStartTime)) / Stopwatch.Frequency; |
| m_Metrics.totalEventLagTime += totalEventLag; |
|
|
| m_InputEventStream.Close(ref eventBuffer); |
| } |
| catch (Exception) |
| { |
| |
| |
| m_InputEventStream.CleanUpAfterException(); |
| throw; |
| } |
|
|
| if (shouldProcessActionTimeouts) |
| ProcessStateChangeMonitorTimeouts(); |
|
|
| Profiler.EndSample(); |
|
|
| |
| |
| |
| InvokeAfterUpdateCallback(updateType); |
| m_CurrentUpdate = default; |
| } |
|
|
| private void InvokeAfterUpdateCallback(InputUpdateType updateType) |
| { |
| |
| |
| |
| if (updateType == InputUpdateType.Editor && gameIsPlaying) |
| return; |
|
|
| DelegateHelpers.InvokeCallbacksSafe(ref m_AfterUpdateListeners, |
| "InputSystem.onAfterUpdate"); |
| } |
|
|
| private bool m_ShouldMakeCurrentlyUpdatingDeviceCurrent; |
|
|
| |
| |
| internal void DontMakeCurrentlyUpdatingDeviceCurrent() |
| { |
| m_ShouldMakeCurrentlyUpdatingDeviceCurrent = false; |
| } |
|
|
| internal unsafe bool UpdateState(InputDevice device, InputEvent* eventPtr, InputUpdateType updateType) |
| { |
| Debug.Assert(eventPtr != null, "Received NULL event ptr"); |
|
|
| var stateBlockOfDevice = device.m_StateBlock; |
| var stateBlockSizeOfDevice = stateBlockOfDevice.sizeInBits / 8; |
| var offsetInDeviceStateToCopyTo = 0u; |
| uint sizeOfStateToCopy; |
| uint receivedStateSize; |
| byte* ptrToReceivedState; |
| FourCC receivedStateFormat; |
|
|
| |
| if (eventPtr->type == StateEvent.Type) |
| { |
| var stateEventPtr = (StateEvent*)eventPtr; |
| receivedStateFormat = stateEventPtr->stateFormat; |
| receivedStateSize = stateEventPtr->stateSizeInBytes; |
| ptrToReceivedState = (byte*)stateEventPtr->state; |
|
|
| |
| sizeOfStateToCopy = receivedStateSize; |
| if (sizeOfStateToCopy > stateBlockSizeOfDevice) |
| sizeOfStateToCopy = stateBlockSizeOfDevice; |
| } |
| else |
| { |
| Debug.Assert(eventPtr->type == DeltaStateEvent.Type, "Given event must either be a StateEvent or a DeltaStateEvent"); |
|
|
| var deltaEventPtr = (DeltaStateEvent*)eventPtr; |
| receivedStateFormat = deltaEventPtr->stateFormat; |
| receivedStateSize = deltaEventPtr->deltaStateSizeInBytes; |
| ptrToReceivedState = (byte*)deltaEventPtr->deltaState; |
| offsetInDeviceStateToCopyTo = deltaEventPtr->stateOffset; |
|
|
| |
| sizeOfStateToCopy = receivedStateSize; |
| if (offsetInDeviceStateToCopyTo + sizeOfStateToCopy > stateBlockSizeOfDevice) |
| { |
| if (offsetInDeviceStateToCopyTo >= stateBlockSizeOfDevice) |
| return false; |
|
|
| sizeOfStateToCopy = stateBlockSizeOfDevice - offsetInDeviceStateToCopyTo; |
| } |
| } |
|
|
| Debug.Assert(device.m_StateBlock.format == receivedStateFormat, "Received state format does not match format of device"); |
|
|
| |
| return UpdateState(device, updateType, ptrToReceivedState, offsetInDeviceStateToCopyTo, |
| sizeOfStateToCopy, eventPtr->internalTime, eventPtr); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| internal unsafe bool UpdateState(InputDevice device, InputUpdateType updateType, |
| void* statePtr, uint stateOffsetInDevice, uint stateSize, double internalTime, InputEventPtr eventPtr = default) |
| { |
| var deviceIndex = device.m_DeviceIndex; |
| ref var stateBlockOfDevice = ref device.m_StateBlock; |
|
|
| |
|
|
| var deviceBuffer = (byte*)InputStateBuffers.GetFrontBufferForDevice(deviceIndex); |
|
|
| |
| |
| SortStateChangeMonitorsIfNecessary(deviceIndex); |
|
|
| |
| |
| |
| |
| |
| |
| var haveSignalledMonitors = |
| ProcessStateChangeMonitors(deviceIndex, statePtr, |
| deviceBuffer + stateBlockOfDevice.byteOffset, |
| stateSize, stateOffsetInDevice); |
|
|
| var deviceStateOffset = device.m_StateBlock.byteOffset + stateOffsetInDevice; |
| var deviceStatePtr = deviceBuffer + deviceStateOffset; |
|
|
| |
| |
| |
| var noiseMask = device.noisy |
| ? (byte*)InputStateBuffers.s_NoiseMaskBuffer + deviceStateOffset |
| : null; |
| |
| |
| var makeDeviceCurrent = !MemoryHelpers.MemCmpBitRegion(deviceStatePtr, statePtr, |
| 0, stateSize * 8, mask: noiseMask); |
|
|
| |
| var flipped = FlipBuffersForDeviceIfNecessary(device, updateType); |
|
|
| |
| #if UNITY_EDITOR |
| if (updateType == InputUpdateType.Editor) |
| { |
| WriteStateChange(m_StateBuffers.m_EditorStateBuffers, deviceIndex, ref stateBlockOfDevice, stateOffsetInDevice, |
| statePtr, stateSize, flipped); |
| } |
| else |
| #endif |
| { |
| WriteStateChange(m_StateBuffers.m_PlayerStateBuffers, deviceIndex, ref stateBlockOfDevice, |
| stateOffsetInDevice, statePtr, stateSize, flipped); |
| } |
|
|
| |
| DelegateHelpers.InvokeCallbacksSafe(ref m_DeviceStateChangeListeners, |
| device, eventPtr, "InputSystem.onDeviceStateChange"); |
|
|
| |
| |
| if (haveSignalledMonitors) |
| FireStateChangeNotifications(deviceIndex, internalTime, eventPtr); |
|
|
| return makeDeviceCurrent; |
| } |
|
|
| private unsafe void WriteStateChange(InputStateBuffers.DoubleBuffers buffers, int deviceIndex, |
| ref InputStateBlock deviceStateBlock, uint stateOffsetInDevice, void* statePtr, uint stateSizeInBytes, bool flippedBuffers) |
| { |
| var frontBuffer = buffers.GetFrontBuffer(deviceIndex); |
| Debug.Assert(frontBuffer != null); |
|
|
| |
| |
| |
| |
| |
| |
| |
| var deviceStateSize = deviceStateBlock.sizeInBits / 8; |
| if (flippedBuffers && deviceStateSize != stateSizeInBytes) |
| { |
| var backBuffer = buffers.GetBackBuffer(deviceIndex); |
| Debug.Assert(backBuffer != null); |
|
|
| UnsafeUtility.MemCpy( |
| (byte*)frontBuffer + deviceStateBlock.byteOffset, |
| (byte*)backBuffer + deviceStateBlock.byteOffset, |
| deviceStateSize); |
| } |
|
|
| if (InputSettings.readValueCachingFeatureEnabled) |
| { |
| |
| |
| |
| var buffer = (byte*)frontBuffer; |
| if (flippedBuffers && deviceStateSize == stateSizeInBytes) |
| buffer = (byte*)buffers.GetBackBuffer(deviceIndex); |
|
|
| m_Devices[deviceIndex].WriteChangedControlStates(buffer + deviceStateBlock.byteOffset, statePtr, |
| stateSizeInBytes, stateOffsetInDevice); |
| } |
|
|
| UnsafeUtility.MemCpy((byte*)frontBuffer + deviceStateBlock.byteOffset + stateOffsetInDevice, statePtr, |
| stateSizeInBytes); |
| } |
|
|
| |
| |
| |
| private bool FlipBuffersForDeviceIfNecessary(InputDevice device, InputUpdateType updateType) |
| { |
| if (updateType == InputUpdateType.BeforeRender) |
| { |
| |
| |
| |
| return false; |
| } |
|
|
| #if UNITY_EDITOR |
| |
| |
| |
| |
| if (updateType == InputUpdateType.Editor) |
| { |
| |
| |
| |
| |
| m_StateBuffers.m_EditorStateBuffers.SwapBuffers(device.m_DeviceIndex); |
| return true; |
| } |
| #endif |
|
|
| |
| if (device.m_CurrentUpdateStepCount != InputUpdate.s_UpdateStepCount) |
| { |
| m_StateBuffers.m_PlayerStateBuffers.SwapBuffers(device.m_DeviceIndex); |
| device.m_CurrentUpdateStepCount = InputUpdate.s_UpdateStepCount; |
| return true; |
| } |
|
|
| return false; |
| } |
|
|
| |
| |
|
|
| |
| |
| #if UNITY_EDITOR || DEVELOPMENT_BUILD |
| [Serializable] |
| internal struct DeviceState |
| { |
| |
| |
| |
| |
| |
| |
| |
| public string name; |
| public string layout; |
| public string variants; |
| public string[] usages; |
| public int deviceId; |
| public int participantId; |
| public InputDevice.DeviceFlags flags; |
| public InputDeviceDescription description; |
|
|
| public void Restore(InputDevice device) |
| { |
| var usageCount = usages.LengthSafe(); |
| for (var i = 0; i < usageCount; ++i) |
| device.AddDeviceUsage(new InternedString(usages[i])); |
| device.m_ParticipantId = participantId; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| [Serializable] |
| internal struct SerializedState |
| { |
| public int layoutRegistrationVersion; |
| public float pollingFrequency; |
| public DeviceState[] devices; |
| public AvailableDevice[] availableDevices; |
| public InputStateBuffers buffers; |
| public InputUpdate.SerializedState updateState; |
| public InputUpdateType updateMask; |
| public InputMetrics metrics; |
| public InputSettings settings; |
|
|
| #if UNITY_ANALYTICS || UNITY_EDITOR |
| public bool haveSentStartupAnalytics; |
| #endif |
| } |
|
|
| internal SerializedState SaveState() |
| { |
| |
| var deviceCount = m_DevicesCount; |
| var deviceArray = new DeviceState[deviceCount]; |
| for (var i = 0; i < deviceCount; ++i) |
| { |
| var device = m_Devices[i]; |
| string[] usages = null; |
| if (device.usages.Count > 0) |
| usages = device.usages.Select(x => x.ToString()).ToArray(); |
|
|
| var deviceState = new DeviceState |
| { |
| name = device.name, |
| layout = device.layout, |
| variants = device.variants, |
| deviceId = device.deviceId, |
| participantId = device.m_ParticipantId, |
| usages = usages, |
| description = device.m_Description, |
| flags = device.m_DeviceFlags |
| }; |
| deviceArray[i] = deviceState; |
| } |
|
|
| return new SerializedState |
| { |
| layoutRegistrationVersion = m_LayoutRegistrationVersion, |
| pollingFrequency = m_PollingFrequency, |
| devices = deviceArray, |
| availableDevices = m_AvailableDevices?.Take(m_AvailableDeviceCount).ToArray(), |
| buffers = m_StateBuffers, |
| updateState = InputUpdate.Save(), |
| updateMask = m_UpdateMask, |
| metrics = m_Metrics, |
| settings = m_Settings, |
|
|
| #if UNITY_ANALYTICS || UNITY_EDITOR |
| haveSentStartupAnalytics = m_HaveSentStartupAnalytics, |
| #endif |
| }; |
| } |
|
|
| internal void RestoreStateWithoutDevices(SerializedState state) |
| { |
| m_StateBuffers = state.buffers; |
| m_LayoutRegistrationVersion = state.layoutRegistrationVersion + 1; |
| updateMask = state.updateMask; |
| m_Metrics = state.metrics; |
| m_PollingFrequency = state.pollingFrequency; |
|
|
| if (m_Settings != null) |
| Object.DestroyImmediate(m_Settings); |
| m_Settings = state.settings; |
|
|
| #if UNITY_ANALYTICS || UNITY_EDITOR |
| m_HaveSentStartupAnalytics = state.haveSentStartupAnalytics; |
| #endif |
|
|
| |
|
|
| |
| InputUpdate.Restore(state.updateState); |
| } |
|
|
| |
| internal DeviceState[] m_SavedDeviceStates; |
| internal AvailableDevice[] m_SavedAvailableDevices; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| internal void RestoreDevicesAfterDomainReload() |
| { |
| Profiler.BeginSample("InputManager.RestoreDevicesAfterDomainReload"); |
|
|
| using (InputDeviceBuilder.Ref()) |
| { |
| DeviceState[] retainedDeviceStates = null; |
| var deviceStates = m_SavedDeviceStates; |
| var deviceCount = m_SavedDeviceStates.LengthSafe(); |
| m_SavedDeviceStates = null; |
| for (var i = 0; i < deviceCount; ++i) |
| { |
| ref var deviceState = ref deviceStates[i]; |
|
|
| var device = TryGetDeviceById(deviceState.deviceId); |
| if (device != null) |
| continue; |
|
|
| var layout = TryFindMatchingControlLayout(ref deviceState.description, |
| deviceState.deviceId); |
| if (layout.IsEmpty()) |
| { |
| var previousLayout = new InternedString(deviceState.layout); |
| if (m_Layouts.HasLayout(previousLayout)) |
| layout = previousLayout; |
| } |
| if (layout.IsEmpty() || !RestoreDeviceFromSavedState(ref deviceState, layout)) |
| ArrayHelpers.Append(ref retainedDeviceStates, deviceState); |
| } |
|
|
| |
| |
| |
| if (m_SavedAvailableDevices != null) |
| { |
| m_AvailableDevices = m_SavedAvailableDevices; |
| m_AvailableDeviceCount = m_SavedAvailableDevices.LengthSafe(); |
| for (var i = 0; i < m_AvailableDeviceCount; ++i) |
| { |
| var device = TryGetDeviceById(m_AvailableDevices[i].deviceId); |
| if (device != null) |
| continue; |
|
|
| if (m_AvailableDevices[i].isRemoved) |
| continue; |
|
|
| var layout = TryFindMatchingControlLayout(ref m_AvailableDevices[i].description, |
| m_AvailableDevices[i].deviceId); |
| if (!layout.IsEmpty()) |
| { |
| try |
| { |
| AddDevice(layout, m_AvailableDevices[i].deviceId, |
| deviceDescription: m_AvailableDevices[i].description, |
| deviceFlags: m_AvailableDevices[i].isNative ? InputDevice.DeviceFlags.Native : 0); |
| } |
| catch (Exception) |
| { |
| |
| } |
| } |
| } |
| } |
|
|
| |
| m_SavedDeviceStates = retainedDeviceStates; |
| m_SavedAvailableDevices = null; |
| } |
|
|
| Profiler.EndSample(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| private bool RestoreDeviceFromSavedState(ref DeviceState deviceState, InternedString layout) |
| { |
| |
| |
| |
|
|
| InputDevice device; |
| try |
| { |
| device = AddDevice(layout, |
| deviceDescription: deviceState.description, |
| deviceId: deviceState.deviceId, |
| deviceName: deviceState.name, |
| deviceFlags: deviceState.flags, |
| variants: new InternedString(deviceState.variants)); |
| } |
| catch (Exception exception) |
| { |
| Debug.LogError( |
| $"Could not recreate input device '{deviceState.description}' with layout '{deviceState.layout}' and variants '{deviceState.variants}' after domain reload"); |
| Debug.LogException(exception); |
| return true; |
| } |
|
|
| deviceState.Restore(device); |
|
|
| return true; |
| } |
|
|
| #endif // UNITY_EDITOR || DEVELOPMENT_BUILD |
| } |
| } |
|
|