A game about forced loneliness, made by TACStudios
1using System; 2using System.Collections.Generic; 3using System.Diagnostics; 4using System.Linq; 5using System.Reflection; 6using System.Runtime.CompilerServices; 7using System.Text; 8using Unity.Collections; 9using UnityEngine.InputSystem.Composites; 10using UnityEngine.InputSystem.Controls; 11using Unity.Collections.LowLevel.Unsafe; 12using UnityEngine.InputSystem.LowLevel; 13using UnityEngine.InputSystem.Processors; 14using UnityEngine.InputSystem.Interactions; 15using UnityEngine.InputSystem.Utilities; 16using UnityEngine.InputSystem.Layouts; 17using Unity.Profiling; 18 19#if UNITY_EDITOR 20using UnityEngine.InputSystem.Editor; 21#endif 22 23#if UNITY_EDITOR 24using CustomBindingPathValidator = System.Func<string, System.Action>; 25#endif 26 27////TODO: make diagnostics available in dev players and give it a public API to enable them 28 29////TODO: work towards InputManager having no direct knowledge of actions 30 31////TODO: allow pushing events into the system any which way; decouple from the buffer in NativeInputSystem being the only source 32 33////REVIEW: change the event properties over to using IObservable? 34 35////REVIEW: instead of RegisterInteraction and RegisterProcessor, have a generic RegisterInterface (or something)? 36 37////REVIEW: can we do away with the 'previous == previous frame' and simply buffer flip on every value write? 38 39////REVIEW: should we force keeping mouse/pen/keyboard/touch around in editor even if not in list of supported devices? 40 41////REVIEW: do we want to filter out state events that result in no state change? 42 43#pragma warning disable CS0649 44namespace UnityEngine.InputSystem 45{ 46 using DeviceChangeListener = Action<InputDevice, InputDeviceChange>; 47 using DeviceStateChangeListener = Action<InputDevice, InputEventPtr>; 48 using LayoutChangeListener = Action<string, InputControlLayoutChange>; 49 using EventListener = Action<InputEventPtr, InputDevice>; 50 using UpdateListener = Action; 51 52 /// <summary> 53 /// Hub of the input system. 54 /// </summary> 55 /// <remarks> 56 /// Not exposed. Use <see cref="InputSystem"/> as the public entry point to the system. 57 /// 58 /// Manages devices, layouts, and event processing. 59 /// </remarks> 60 internal partial class InputManager 61 { 62 public ReadOnlyArray<InputDevice> devices => new ReadOnlyArray<InputDevice>(m_Devices, 0, m_DevicesCount); 63 64 public TypeTable processors => m_Processors; 65 public TypeTable interactions => m_Interactions; 66 public TypeTable composites => m_Composites; 67 68 static readonly ProfilerMarker k_InputUpdateProfilerMarker = new ProfilerMarker("InputUpdate"); 69 static readonly ProfilerMarker k_InputTryFindMatchingControllerMarker = new ProfilerMarker("InputSystem.TryFindMatchingControlLayout"); 70 static readonly ProfilerMarker k_InputAddDeviceMarker = new ProfilerMarker("InputSystem.AddDevice"); 71 static readonly ProfilerMarker k_InputRestoreDevicesAfterReloadMarker = new ProfilerMarker("InputManager.RestoreDevicesAfterDomainReload"); 72 static readonly ProfilerMarker k_InputRegisterCustomTypesMarker = new ProfilerMarker("InputManager.RegisterCustomTypes"); 73 74 static readonly ProfilerMarker k_InputOnBeforeUpdateMarker = new ProfilerMarker("InputSystem.onBeforeUpdate"); 75 static readonly ProfilerMarker k_InputOnAfterUpdateMarker = new ProfilerMarker("InputSystem.onAfterUpdate"); 76 static readonly ProfilerMarker k_InputOnSettingsChangeMarker = new ProfilerMarker("InputSystem.onSettingsChange"); 77 static readonly ProfilerMarker k_InputOnDeviceSettingsChangeMarker = new ProfilerMarker("InputSystem.onDeviceSettingsChange"); 78 static readonly ProfilerMarker k_InputOnEventMarker = new ProfilerMarker("InputSystem.onEvent"); 79 static readonly ProfilerMarker k_InputOnLayoutChangeMarker = new ProfilerMarker("InputSystem.onLayoutChange"); 80 static readonly ProfilerMarker k_InputOnDeviceChangeMarker = new ProfilerMarker("InpustSystem.onDeviceChange"); 81 static readonly ProfilerMarker k_InputOnActionsChangeMarker = new ProfilerMarker("InpustSystem.onActionsChange"); 82 83 84 public InputMetrics metrics 85 { 86 get 87 { 88 var result = m_Metrics; 89 90 result.currentNumDevices = m_DevicesCount; 91 result.currentStateSizeInBytes = (int)m_StateBuffers.totalSize; 92 93 // Count controls. 94 result.currentControlCount = m_DevicesCount; 95 for (var i = 0; i < m_DevicesCount; ++i) 96 result.currentControlCount += m_Devices[i].allControls.Count; 97 98 // Count layouts. 99 result.currentLayoutCount = m_Layouts.layoutTypes.Count; 100 result.currentLayoutCount += m_Layouts.layoutStrings.Count; 101 result.currentLayoutCount += m_Layouts.layoutBuilders.Count; 102 result.currentLayoutCount += m_Layouts.layoutOverrides.Count; 103 104 return result; 105 } 106 } 107 108 public InputSettings settings 109 { 110 get 111 { 112 Debug.Assert(m_Settings != null); 113 return m_Settings; 114 } 115 set 116 { 117 if (value == null) 118 throw new ArgumentNullException(nameof(value)); 119 120 if (m_Settings == value) 121 return; 122 123 m_Settings = value; 124 ApplySettings(); 125 } 126 } 127 128 #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 129 public InputActionAsset actions 130 { 131 get 132 { 133 return m_Actions; 134 } 135 136 set 137 { 138 m_Actions = value; 139 ApplyActions(); 140 } 141 } 142 #endif 143 144 public InputUpdateType updateMask 145 { 146 get => m_UpdateMask; 147 set 148 { 149 // In editor, we don't allow disabling editor updates. 150 #if UNITY_EDITOR 151 value |= InputUpdateType.Editor; 152 #endif 153 154 if (m_UpdateMask == value) 155 return; 156 157 m_UpdateMask = value; 158 159 // Recreate state buffers. 160 if (m_DevicesCount > 0) 161 ReallocateStateBuffers(); 162 } 163 } 164 165 public InputUpdateType defaultUpdateType 166 { 167 get 168 { 169 if (m_CurrentUpdate != default) 170 return m_CurrentUpdate; 171 172 #if UNITY_EDITOR 173 if (!m_RunPlayerUpdatesInEditMode && (!gameIsPlaying || !gameHasFocus)) 174 return InputUpdateType.Editor; 175 #endif 176 177 return m_UpdateMask.GetUpdateTypeForPlayer(); 178 } 179 } 180 181 public InputSettings.ScrollDeltaBehavior scrollDeltaBehavior 182 { 183 get => m_ScrollDeltaBehavior; 184 set 185 { 186 if (m_ScrollDeltaBehavior == value) 187 return; 188 189 m_ScrollDeltaBehavior = value; 190 191#if UNITY_INPUT_SYSTEM_PLATFORM_SCROLL_DELTA 192 InputRuntime.s_Instance.normalizeScrollWheelDelta = 193 m_ScrollDeltaBehavior == InputSettings.ScrollDeltaBehavior.UniformAcrossAllPlatforms; 194#endif 195 } 196 } 197 198 public float pollingFrequency 199 { 200 get => m_PollingFrequency; 201 set 202 { 203 ////REVIEW: allow setting to zero to turn off polling altogether? 204 if (value <= 0) 205 throw new ArgumentException("Polling frequency must be greater than zero", "value"); 206 207 m_PollingFrequency = value; 208 if (m_Runtime != null) 209 m_Runtime.pollingFrequency = value; 210 } 211 } 212 213 public event DeviceChangeListener onDeviceChange 214 { 215 add => m_DeviceChangeListeners.AddCallback(value); 216 remove => m_DeviceChangeListeners.RemoveCallback(value); 217 } 218 219 public event DeviceStateChangeListener onDeviceStateChange 220 { 221 add => m_DeviceStateChangeListeners.AddCallback(value); 222 remove => m_DeviceStateChangeListeners.RemoveCallback(value); 223 } 224 225 public event InputDeviceCommandDelegate onDeviceCommand 226 { 227 add => m_DeviceCommandCallbacks.AddCallback(value); 228 remove => m_DeviceCommandCallbacks.RemoveCallback(value); 229 } 230 231 ////REVIEW: would be great to have a way to sort out precedence between two callbacks 232 public event InputDeviceFindControlLayoutDelegate onFindControlLayoutForDevice 233 { 234 add 235 { 236 m_DeviceFindLayoutCallbacks.AddCallback(value); 237 238 // Having a new callback on this event can change the set of devices we recognize. 239 // See if there's anything in the list of available devices that we can now turn 240 // into an InputDevice whereas we couldn't before. 241 // 242 // NOTE: A callback could also impact already existing devices and theoretically alter 243 // what layout we would have used for those. We do *NOT* retroactively apply 244 // those changes. 245 AddAvailableDevicesThatAreNowRecognized(); 246 } 247 remove => m_DeviceFindLayoutCallbacks.RemoveCallback(value); 248 } 249 250 public event LayoutChangeListener onLayoutChange 251 { 252 add => m_LayoutChangeListeners.AddCallback(value); 253 remove => m_LayoutChangeListeners.RemoveCallback(value); 254 } 255 256 ////TODO: add InputEventBuffer struct that uses NativeArray underneath 257 ////TODO: make InputEventTrace use NativeArray 258 ////TODO: introduce an alternative that consumes events in bulk 259 public event EventListener onEvent 260 { 261 add => m_EventListeners.AddCallback(value); 262 remove => m_EventListeners.RemoveCallback(value); 263 } 264 265 public event UpdateListener onBeforeUpdate 266 { 267 add 268 { 269 InstallBeforeUpdateHookIfNecessary(); 270 m_BeforeUpdateListeners.AddCallback(value); 271 } 272 remove => m_BeforeUpdateListeners.RemoveCallback(value); 273 } 274 275 public event UpdateListener onAfterUpdate 276 { 277 add => m_AfterUpdateListeners.AddCallback(value); 278 remove => m_AfterUpdateListeners.RemoveCallback(value); 279 } 280 281 public event Action onSettingsChange 282 { 283 add => m_SettingsChangedListeners.AddCallback(value); 284 remove => m_SettingsChangedListeners.RemoveCallback(value); 285 } 286 287 #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 288 public event Action onActionsChange 289 { 290 add => m_ActionsChangedListeners.AddCallback(value); 291 remove => m_ActionsChangedListeners.RemoveCallback(value); 292 } 293 #endif 294 295 public bool isProcessingEvents => m_InputEventStream.isOpen; 296 297#if UNITY_EDITOR 298 /// <summary> 299 /// Callback that can be used to display a warning and draw additional custom Editor UI for bindings. 300 /// </summary> 301 /// <seealso cref="InputSystem.customBindingPathValidators"/> 302 /// <remarks> 303 /// This is not intended to be called directly. 304 /// Please use <see cref="InputSystem.customBindingPathValidators"/> instead. 305 /// </remarks> 306 internal event CustomBindingPathValidator customBindingPathValidators 307 { 308 add => m_customBindingPathValidators.AddCallback(value); 309 remove => m_customBindingPathValidators.RemoveCallback(value); 310 } 311 312 /// <summary> 313 /// Invokes any custom UI rendering code for this Binding Path in the editor. 314 /// </summary> 315 /// <seealso cref="InputSystem.customBindingPathValidators"/> 316 /// <remarks> 317 /// This is not intended to be called directly. 318 /// Please use <see cref="InputSystem.OnDrawCustomWarningForBindingPath"/> instead. 319 /// </remarks> 320 internal void OnDrawCustomWarningForBindingPath(string bindingPath) 321 { 322 DelegateHelpers.InvokeCallbacksSafe_AndInvokeReturnedActions( 323 ref m_customBindingPathValidators, 324 bindingPath, 325 "InputSystem.OnDrawCustomWarningForBindingPath"); 326 } 327 328 /// <summary> 329 /// Determines if any warning icon is to be displayed for this Binding Path in the editor. 330 /// </summary> 331 /// <seealso cref="InputSystem.customBindingPathValidators"/> 332 /// <remarks> 333 /// This is not intended to be called directly. 334 /// Please use <see cref="InputSystem.OnDrawCustomWarningForBindingPath"/> instead. 335 /// </remarks> 336 internal bool ShouldDrawWarningIconForBinding(string bindingPath) 337 { 338 return DelegateHelpers.InvokeCallbacksSafe_AnyCallbackReturnsObject( 339 ref m_customBindingPathValidators, 340 bindingPath, 341 "InputSystem.ShouldDrawWarningIconForBinding"); 342 } 343 344#endif // UNITY_EDITOR 345 346#if UNITY_EDITOR 347 private bool m_RunPlayerUpdatesInEditMode; 348 349 /// <summary> 350 /// If true, consider the editor to be in "perpetual play mode". Meaning, we ignore editor 351 /// updates and just go and continuously process Dynamic/Fixed/BeforeRender regardless of 352 /// whether we're in play mode or not. 353 /// 354 /// In this mode, we also ignore game view focus. 355 /// </summary> 356 public bool runPlayerUpdatesInEditMode 357 { 358 get => m_RunPlayerUpdatesInEditMode; 359 set => m_RunPlayerUpdatesInEditMode = value; 360 } 361#endif 362 363 private bool gameIsPlaying => 364#if UNITY_EDITOR 365 (m_Runtime.isInPlayMode && !m_Runtime.isPaused) || m_RunPlayerUpdatesInEditMode; 366#else 367 true; 368#endif 369 370 private bool gameHasFocus => 371#if UNITY_EDITOR 372 m_RunPlayerUpdatesInEditMode || m_HasFocus || gameShouldGetInputRegardlessOfFocus; 373#else 374 m_HasFocus || gameShouldGetInputRegardlessOfFocus; 375#endif 376 377 private bool gameShouldGetInputRegardlessOfFocus => 378 m_Settings.backgroundBehavior == InputSettings.BackgroundBehavior.IgnoreFocus 379#if UNITY_EDITOR 380 && m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView 381#endif 382 ; 383 384 ////TODO: when registering a layout that exists as a layout of a different type (type vs string vs constructor), 385 //// remove the existing registration 386 387 // Add a layout constructed from a type. 388 // If a layout with the same name already exists, the new layout 389 // takes its place. 390 public void RegisterControlLayout(string name, Type type) 391 { 392 if (string.IsNullOrEmpty(name)) 393 throw new ArgumentNullException(nameof(name)); 394 if (type == null) 395 throw new ArgumentNullException(nameof(type)); 396 397 // Note that since InputDevice derives from InputControl, isDeviceLayout implies 398 // isControlLayout to be true as well. 399 var isDeviceLayout = typeof(InputDevice).IsAssignableFrom(type); 400 var isControlLayout = typeof(InputControl).IsAssignableFrom(type); 401 402 if (!isDeviceLayout && !isControlLayout) 403 throw new ArgumentException($"Types used as layouts have to be InputControls or InputDevices; '{type.Name}' is a '{type.BaseType.Name}'", 404 nameof(type)); 405 406 var internedName = new InternedString(name); 407 var isReplacement = m_Layouts.HasLayout(internedName); 408 409 // All we do is enter the type into a map. We don't construct an InputControlLayout 410 // from it until we actually need it in an InputDeviceBuilder to create a device. 411 // This not only avoids us creating a bunch of objects on the managed heap but 412 // also avoids us laboriously constructing a XRController layout, for example, 413 // in a game that never uses XR. 414 m_Layouts.layoutTypes[internedName] = type; 415 416 ////TODO: make this independent of initialization order 417 ////TODO: re-scan base type information after domain reloads 418 419 // Walk class hierarchy all the way up to InputControl to see 420 // if there's another type that's been registered as a layout. 421 // If so, make it a base layout for this one. 422 string baseLayout = null; 423 for (var baseType = type.BaseType; baseLayout == null && baseType != typeof(InputControl); 424 baseType = baseType.BaseType) 425 { 426 foreach (var entry in m_Layouts.layoutTypes) 427 if (entry.Value == baseType) 428 { 429 baseLayout = entry.Key; 430 break; 431 } 432 } 433 434 PerformLayoutPostRegistration(internedName, new InlinedArray<InternedString>(new InternedString(baseLayout)), 435 isReplacement, isKnownToBeDeviceLayout: isDeviceLayout); 436 } 437 438 public void RegisterControlLayout(string json, string name = null, bool isOverride = false) 439 { 440 if (string.IsNullOrEmpty(json)) 441 throw new ArgumentNullException(nameof(json)); 442 443 ////REVIEW: as long as no one has instantiated the layout, the base layout information is kinda pointless 444 445 // Parse out name, device description, and base layout. 446 InputControlLayout.ParseHeaderFieldsFromJson(json, out var nameFromJson, out var baseLayouts, 447 out var deviceMatcher); 448 449 // Decide whether to take name from JSON or from code. 450 var internedLayoutName = new InternedString(name); 451 if (internedLayoutName.IsEmpty()) 452 { 453 internedLayoutName = nameFromJson; 454 455 // Make sure we have a name. 456 if (internedLayoutName.IsEmpty()) 457 throw new ArgumentException("Layout name has not been given and is not set in JSON layout", 458 nameof(name)); 459 } 460 461 // If it's an override, it must have a layout the overrides apply to. 462 if (isOverride && baseLayouts.length == 0) 463 { 464 throw new ArgumentException( 465 $"Layout override '{internedLayoutName}' must have 'extend' property mentioning layout to which to apply the overrides", 466 nameof(json)); 467 } 468 469 // Add it to our records. 470 var isReplacement = m_Layouts.HasLayout(internedLayoutName); 471 if (isReplacement && isOverride) 472 { // Do not allow a layout override to replace a "base layout" by name, but allow layout overrides 473 // to replace an existing layout override. 474 // This is required to guarantee that its a hierarchy (directed graph) rather 475 // than a cyclic graph. 476 477 var isReplacingOverride = m_Layouts.layoutOverrideNames.Contains(internedLayoutName); 478 if (!isReplacingOverride) 479 { 480 throw new ArgumentException($"Failed to register layout override '{internedLayoutName}'" + 481 $"since a layout named '{internedLayoutName}' already exist. Layout overrides must " + 482 $"have unique names with respect to existing layouts."); 483 } 484 } 485 486 m_Layouts.layoutStrings[internedLayoutName] = json; 487 if (isOverride) 488 { 489 m_Layouts.layoutOverrideNames.Add(internedLayoutName); 490 for (var i = 0; i < baseLayouts.length; ++i) 491 { 492 var baseLayoutName = baseLayouts[i]; 493 m_Layouts.layoutOverrides.TryGetValue(baseLayoutName, out var overrideList); 494 if (!isReplacement) 495 ArrayHelpers.Append(ref overrideList, internedLayoutName); 496 497 498 m_Layouts.layoutOverrides[baseLayoutName] = overrideList; 499 } 500 } 501 502 PerformLayoutPostRegistration(internedLayoutName, baseLayouts, 503 isReplacement: isReplacement, isOverride: isOverride); 504 505 // If the layout contained a device matcher, register it. 506 if (!deviceMatcher.empty) 507 RegisterControlLayoutMatcher(internedLayoutName, deviceMatcher); 508 } 509 510 public void RegisterControlLayoutBuilder(Func<InputControlLayout> method, string name, 511 string baseLayout = null) 512 { 513 if (method == null) 514 throw new ArgumentNullException(nameof(method)); 515 if (string.IsNullOrEmpty(name)) 516 throw new ArgumentNullException(nameof(name)); 517 518 var internedLayoutName = new InternedString(name); 519 var internedBaseLayoutName = new InternedString(baseLayout); 520 var isReplacement = m_Layouts.HasLayout(internedLayoutName); 521 522 m_Layouts.layoutBuilders[internedLayoutName] = method; 523 524 PerformLayoutPostRegistration(internedLayoutName, new InlinedArray<InternedString>(internedBaseLayoutName), 525 isReplacement); 526 } 527 528 private void PerformLayoutPostRegistration(InternedString layoutName, InlinedArray<InternedString> baseLayouts, 529 bool isReplacement, bool isKnownToBeDeviceLayout = false, bool isOverride = false) 530 { 531 ++m_LayoutRegistrationVersion; 532 533 // Force-clear layout cache. Don't clear reference count so that 534 // the cache gets cleared out properly when released in case someone 535 // is using it ATM. 536 InputControlLayout.s_CacheInstance.Clear(); 537 538 // For layouts that aren't overrides, add the name of the base 539 // layout to the lookup table. 540 if (!isOverride && baseLayouts.length > 0) 541 { 542 if (baseLayouts.length > 1) 543 throw new NotSupportedException( 544 $"Layout '{layoutName}' has multiple base layouts; this is only supported on layout overrides"); 545 546 var baseLayoutName = baseLayouts[0]; 547 if (!baseLayoutName.IsEmpty()) 548 m_Layouts.baseLayoutTable[layoutName] = baseLayoutName; 549 } 550 551 // Nuke any precompiled layouts that are invalidated by the layout registration. 552 m_Layouts.precompiledLayouts.Remove(layoutName); 553 if (m_Layouts.precompiledLayouts.Count > 0) 554 { 555 foreach (var layout in m_Layouts.precompiledLayouts.Keys.ToArray()) 556 { 557 var metadata = m_Layouts.precompiledLayouts[layout].metadata; 558 559 // If it's an override, we remove any precompiled layouts to which overrides are applied. 560 if (isOverride) 561 { 562 for (var i = 0; i < baseLayouts.length; ++i) 563 if (layout == baseLayouts[i] || 564 StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(metadata, 565 baseLayouts[i], ';')) 566 m_Layouts.precompiledLayouts.Remove(layout); 567 } 568 else 569 { 570 // Otherwise, we remove any precompile layouts that use the layout we just changed. 571 if (StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(metadata, 572 layoutName, ';')) 573 m_Layouts.precompiledLayouts.Remove(layout); 574 } 575 } 576 } 577 578 // Recreate any devices using the layout. If it's an override, recreate devices using any of the base layouts. 579 if (isOverride) 580 { 581 for (var i = 0; i < baseLayouts.length; ++i) 582 RecreateDevicesUsingLayout(baseLayouts[i], isKnownToBeDeviceLayout: isKnownToBeDeviceLayout); 583 } 584 else 585 { 586 RecreateDevicesUsingLayout(layoutName, isKnownToBeDeviceLayout: isKnownToBeDeviceLayout); 587 } 588 589 // In the editor, layouts may become available successively after a domain reload so 590 // we may end up retaining device information all the way until we run the first full 591 // player update. For every layout we register, we check here whether we have a saved 592 // device state using a layout with the same name but not having a device description 593 // (the latter is important as in that case, we should go through the normal matching 594 // process and not just rely on the name of the layout). If so, we try here to recreate 595 // the device with the just registered layout. 596 #if UNITY_EDITOR 597 for (var i = 0; i < m_SavedDeviceStates.LengthSafe(); ++i) 598 { 599 ref var deviceState = ref m_SavedDeviceStates[i]; 600 if (layoutName != deviceState.layout || !deviceState.description.empty) 601 continue; 602 603 if (RestoreDeviceFromSavedState(ref deviceState, layoutName)) 604 { 605 ArrayHelpers.EraseAt(ref m_SavedDeviceStates, i); 606 --i; 607 } 608 } 609 #endif 610 611 // Let listeners know. 612 var change = isReplacement ? InputControlLayoutChange.Replaced : InputControlLayoutChange.Added; 613 DelegateHelpers.InvokeCallbacksSafe(ref m_LayoutChangeListeners, layoutName.ToString(), change, k_InputOnLayoutChangeMarker, "InputSystem.onLayoutChange"); 614 } 615 616 public void RegisterPrecompiledLayout<TDevice>(string metadata) 617 where TDevice : InputDevice, new() 618 { 619 if (metadata == null) 620 throw new ArgumentNullException(nameof(metadata)); 621 622 var deviceType = typeof(TDevice).BaseType; 623 var layoutName = FindOrRegisterDeviceLayoutForType(deviceType); 624 625 m_Layouts.precompiledLayouts[layoutName] = new InputControlLayout.Collection.PrecompiledLayout 626 { 627 factoryMethod = () => new TDevice(), 628 metadata = metadata 629 }; 630 } 631 632 private void RecreateDevicesUsingLayout(InternedString layout, bool isKnownToBeDeviceLayout = false) 633 { 634 if (m_DevicesCount == 0) 635 return; 636 637 List<InputDevice> devicesUsingLayout = null; 638 639 // Find all devices using the layout. 640 for (var i = 0; i < m_DevicesCount; ++i) 641 { 642 var device = m_Devices[i]; 643 644 bool usesLayout; 645 if (isKnownToBeDeviceLayout) 646 usesLayout = IsControlUsingLayout(device, layout); 647 else 648 usesLayout = IsControlOrChildUsingLayoutRecursive(device, layout); 649 650 if (usesLayout) 651 { 652 if (devicesUsingLayout == null) 653 devicesUsingLayout = new List<InputDevice>(); 654 devicesUsingLayout.Add(device); 655 } 656 } 657 658 // If there's none, we're good. 659 if (devicesUsingLayout == null) 660 return; 661 662 // Remove and re-add the matching devices. 663 using (InputDeviceBuilder.Ref()) 664 { 665 for (var i = 0; i < devicesUsingLayout.Count; ++i) 666 { 667 var device = devicesUsingLayout[i]; 668 RecreateDevice(device, device.m_Layout); 669 } 670 } 671 } 672 673 private bool IsControlOrChildUsingLayoutRecursive(InputControl control, InternedString layout) 674 { 675 // Check control itself. 676 if (IsControlUsingLayout(control, layout)) 677 return true; 678 679 // Check children. 680 var children = control.children; 681 for (var i = 0; i < children.Count; ++i) 682 if (IsControlOrChildUsingLayoutRecursive(children[i], layout)) 683 return true; 684 685 return false; 686 } 687 688 private bool IsControlUsingLayout(InputControl control, InternedString layout) 689 { 690 // Check direct match. 691 if (control.layout == layout) 692 return true; 693 694 // Check base layout chain. 695 var baseLayout = control.m_Layout; 696 while (m_Layouts.baseLayoutTable.TryGetValue(baseLayout, out baseLayout)) 697 if (baseLayout == layout) 698 return true; 699 700 return false; 701 } 702 703 public void RegisterControlLayoutMatcher(string layoutName, InputDeviceMatcher matcher) 704 { 705 if (string.IsNullOrEmpty(layoutName)) 706 throw new ArgumentNullException(nameof(layoutName)); 707 if (matcher.empty) 708 throw new ArgumentException("Matcher cannot be empty", nameof(matcher)); 709 710 // Add to table. 711 var internedLayoutName = new InternedString(layoutName); 712 m_Layouts.AddMatcher(internedLayoutName, matcher); 713 714 // Recreate any device that we match better than its current layout. 715 RecreateDevicesUsingLayoutWithInferiorMatch(matcher); 716 717 // See if we can make sense of any device we couldn't make sense of before. 718 AddAvailableDevicesMatchingDescription(matcher, internedLayoutName); 719 } 720 721 public void RegisterControlLayoutMatcher(Type type, InputDeviceMatcher matcher) 722 { 723 if (type == null) 724 throw new ArgumentNullException(nameof(type)); 725 if (matcher.empty) 726 throw new ArgumentException("Matcher cannot be empty", nameof(matcher)); 727 728 var layoutName = m_Layouts.TryFindLayoutForType(type); 729 if (layoutName.IsEmpty()) 730 throw new ArgumentException( 731 $"Type '{type.Name}' has not been registered as a control layout", nameof(type)); 732 733 RegisterControlLayoutMatcher(layoutName, matcher); 734 } 735 736 private void RecreateDevicesUsingLayoutWithInferiorMatch(InputDeviceMatcher deviceMatcher) 737 { 738 if (m_DevicesCount == 0) 739 return; 740 741 using (InputDeviceBuilder.Ref()) 742 { 743 var deviceCount = m_DevicesCount; 744 for (var i = 0; i < deviceCount; ++i) 745 { 746 var device = m_Devices[i]; 747 var deviceDescription = device.description; 748 749 if (deviceDescription.empty || !(deviceMatcher.MatchPercentage(deviceDescription) > 0)) 750 continue; 751 752 var layoutName = TryFindMatchingControlLayout(ref deviceDescription, device.deviceId); 753 if (layoutName != device.m_Layout) 754 { 755 device.m_Description = deviceDescription; 756 757 RecreateDevice(device, layoutName); 758 759 // We're removing devices in the middle of the array and appending 760 // them at the end. Adjust our index and device count to make sure 761 // we're not iterating all the way into already processed devices. 762 763 --i; 764 --deviceCount; 765 } 766 } 767 } 768 } 769 770 private void RecreateDevice(InputDevice oldDevice, InternedString newLayout) 771 { 772 // Remove. 773 RemoveDevice(oldDevice, keepOnListOfAvailableDevices: true); 774 775 // Re-setup device. 776 var newDevice = InputDevice.Build<InputDevice>(newLayout, oldDevice.m_Variants, 777 deviceDescription: oldDevice.m_Description); 778 779 // Preserve device properties that should not be changed by the re-creation 780 // of a device. 781 newDevice.m_DeviceId = oldDevice.m_DeviceId; 782 newDevice.m_Description = oldDevice.m_Description; 783 if (oldDevice.native) 784 newDevice.m_DeviceFlags |= InputDevice.DeviceFlags.Native; 785 if (oldDevice.remote) 786 newDevice.m_DeviceFlags |= InputDevice.DeviceFlags.Remote; 787 if (!oldDevice.enabled) 788 { 789 newDevice.m_DeviceFlags |= InputDevice.DeviceFlags.DisabledStateHasBeenQueriedFromRuntime; 790 newDevice.m_DeviceFlags |= InputDevice.DeviceFlags.DisabledInFrontend; 791 } 792 793 // Re-add. 794 AddDevice(newDevice); 795 } 796 797 private void AddAvailableDevicesMatchingDescription(InputDeviceMatcher matcher, InternedString layout) 798 { 799 #if UNITY_EDITOR 800 // If we still have some devices saved from the last domain reload, see 801 // if they are matched by the given matcher. If so, turn them into devices. 802 for (var i = 0; i < m_SavedDeviceStates.LengthSafe(); ++i) 803 { 804 ref var deviceState = ref m_SavedDeviceStates[i]; 805 if (matcher.MatchPercentage(deviceState.description) > 0) 806 { 807 RestoreDeviceFromSavedState(ref deviceState, layout); 808 ArrayHelpers.EraseAt(ref m_SavedDeviceStates, i); 809 --i; 810 } 811 } 812 #endif 813 814 // See if the new description to layout mapping allows us to make 815 // sense of a device we couldn't make sense of so far. 816 for (var i = 0; i < m_AvailableDeviceCount; ++i) 817 { 818 // Ignore if it's a device that has been explicitly removed. 819 if (m_AvailableDevices[i].isRemoved) 820 continue; 821 822 var deviceId = m_AvailableDevices[i].deviceId; 823 if (TryGetDeviceById(deviceId) != null) 824 continue; 825 826 if (matcher.MatchPercentage(m_AvailableDevices[i].description) > 0f) 827 { 828 // Try to create InputDevice instance. 829 try 830 { 831 AddDevice(layout, deviceId, deviceDescription: m_AvailableDevices[i].description, 832 deviceFlags: m_AvailableDevices[i].isNative ? InputDevice.DeviceFlags.Native : 0); 833 } 834 catch (Exception exception) 835 { 836 Debug.LogError( 837 $"Layout '{layout}' matches existing device '{m_AvailableDevices[i].description}' but failed to instantiate: {exception}"); 838 Debug.LogException(exception); 839 continue; 840 } 841 842 // Re-enable device. 843 var command = EnableDeviceCommand.Create(); 844 m_Runtime.DeviceCommand(deviceId, ref command); 845 } 846 } 847 } 848 849 public void RemoveControlLayout(string name) 850 { 851 if (string.IsNullOrEmpty(name)) 852 throw new ArgumentNullException(nameof(name)); 853 854 var internedName = new InternedString(name); 855 856 // Remove all devices using the layout. 857 for (var i = 0; i < m_DevicesCount;) 858 { 859 var device = m_Devices[i]; 860 if (IsControlOrChildUsingLayoutRecursive(device, internedName)) 861 { 862 RemoveDevice(device, keepOnListOfAvailableDevices: true); 863 } 864 else 865 { 866 ++i; 867 } 868 } 869 870 // Remove layout record. 871 m_Layouts.layoutTypes.Remove(internedName); 872 m_Layouts.layoutStrings.Remove(internedName); 873 m_Layouts.layoutBuilders.Remove(internedName); 874 m_Layouts.baseLayoutTable.Remove(internedName); 875 ++m_LayoutRegistrationVersion; 876 877 ////TODO: check all layout inheritance chain for whether they are based on the layout and if so 878 //// remove those layouts, too 879 880 // Let listeners know. 881 DelegateHelpers.InvokeCallbacksSafe(ref m_LayoutChangeListeners, name, InputControlLayoutChange.Removed, k_InputOnLayoutChangeMarker, "InputSystem.onLayoutChange"); 882 } 883 884 public InputControlLayout TryLoadControlLayout(Type type) 885 { 886 if (type == null) 887 throw new ArgumentNullException(nameof(type)); 888 if (!typeof(InputControl).IsAssignableFrom(type)) 889 throw new ArgumentException($"Type '{type.Name}' is not an InputControl", nameof(type)); 890 891 // Find the layout name that the given type was registered with. 892 var layoutName = m_Layouts.TryFindLayoutForType(type); 893 if (layoutName.IsEmpty()) 894 throw new ArgumentException( 895 $"Type '{type.Name}' has not been registered as a control layout", nameof(type)); 896 897 return m_Layouts.TryLoadLayout(layoutName); 898 } 899 900 public InputControlLayout TryLoadControlLayout(InternedString name) 901 { 902 return m_Layouts.TryLoadLayout(name); 903 } 904 905 ////FIXME: allowing the description to be modified as part of this is surprising; find a better way 906 public InternedString TryFindMatchingControlLayout(ref InputDeviceDescription deviceDescription, int deviceId = InputDevice.InvalidDeviceId) 907 { 908 InternedString layoutName = new InternedString(string.Empty); 909 try 910 { 911 k_InputTryFindMatchingControllerMarker.Begin(); 912 ////TODO: this will want to take overrides into account 913 914 // See if we can match by description. 915 layoutName = m_Layouts.TryFindMatchingLayout(deviceDescription); 916 if (layoutName.IsEmpty()) 917 { 918 // No, so try to match by device class. If we have a "Gamepad" layout, 919 // for example, a device that classifies itself as a "Gamepad" will match 920 // that layout. 921 // 922 // NOTE: Have to make sure here that we get a device layout and not a 923 // control layout. 924 if (!string.IsNullOrEmpty(deviceDescription.deviceClass)) 925 { 926 var deviceClassLowerCase = new InternedString(deviceDescription.deviceClass); 927 var type = m_Layouts.GetControlTypeForLayout(deviceClassLowerCase); 928 if (type != null && typeof(InputDevice).IsAssignableFrom(type)) 929 layoutName = new InternedString(deviceDescription.deviceClass); 930 } 931 } 932 933 ////REVIEW: listeners registering new layouts from in here may potentially lead to the creation of devices; should we disallow that? 934 ////REVIEW: if a callback picks a layout, should we re-run through the list of callbacks? or should we just remove haveOverridenLayoutName? 935 // Give listeners a shot to select/create a layout. 936 if (m_DeviceFindLayoutCallbacks.length > 0) 937 { 938 // First time we get here, put our delegate for executing device commands 939 // in place. We wrap the call to IInputRuntime.DeviceCommand so that we don't 940 // need to expose the runtime to the onFindLayoutForDevice callbacks. 941 if (m_DeviceFindExecuteCommandDelegate == null) 942 m_DeviceFindExecuteCommandDelegate = 943 (ref InputDeviceCommand commandRef) => 944 { 945 if (m_DeviceFindExecuteCommandDeviceId == InputDevice.InvalidDeviceId) 946 return InputDeviceCommand.GenericFailure; 947 return m_Runtime.DeviceCommand(m_DeviceFindExecuteCommandDeviceId, ref commandRef); 948 }; 949 m_DeviceFindExecuteCommandDeviceId = deviceId; 950 951 var haveOverriddenLayoutName = false; 952 m_DeviceFindLayoutCallbacks.LockForChanges(); 953 for (var i = 0; i < m_DeviceFindLayoutCallbacks.length; ++i) 954 { 955 try 956 { 957 var newLayout = m_DeviceFindLayoutCallbacks[i](ref deviceDescription, layoutName, m_DeviceFindExecuteCommandDelegate); 958 if (!string.IsNullOrEmpty(newLayout) && !haveOverriddenLayoutName) 959 { 960 layoutName = new InternedString(newLayout); 961 haveOverriddenLayoutName = true; 962 } 963 } 964 catch (Exception exception) 965 { 966 Debug.LogError($"{exception.GetType().Name} while executing 'InputSystem.onFindLayoutForDevice' callbacks"); 967 Debug.LogException(exception); 968 } 969 } 970 m_DeviceFindLayoutCallbacks.UnlockForChanges(); 971 } 972 } 973 finally 974 { 975 k_InputTryFindMatchingControllerMarker.End(); 976 } 977 return layoutName; 978 } 979 980 private InternedString FindOrRegisterDeviceLayoutForType(Type type) 981 { 982 var layoutName = m_Layouts.TryFindLayoutForType(type); 983 if (layoutName.IsEmpty()) 984 { 985 // Automatically register the given type as a layout. 986 if (layoutName.IsEmpty()) 987 { 988 layoutName = new InternedString(type.Name); 989 RegisterControlLayout(type.Name, type); 990 } 991 } 992 993 return layoutName; 994 } 995 996 /// <summary> 997 /// Return true if the given device layout is supported by the game according to <see cref="InputSettings.supportedDevices"/>. 998 /// </summary> 999 /// <param name="layoutName">Name of the device layout.</param> 1000 /// <returns>True if a device with the given layout should be created for the game, false otherwise.</returns> 1001 private bool IsDeviceLayoutMarkedAsSupportedInSettings(InternedString layoutName) 1002 { 1003 // In the editor, "Supported Devices" can be overridden by a user setting. This causes 1004 // all available devices to be added regardless of what "Supported Devices" says. This 1005 // is useful to ensure that things like keyboard, mouse, and pen keep working in the editor 1006 // even if not supported as devices in the game. 1007 #if UNITY_EDITOR 1008 if (InputEditorUserSettings.addDevicesNotSupportedByProject) 1009 return true; 1010 #endif 1011 1012 var supportedDevices = m_Settings.supportedDevices; 1013 if (supportedDevices.Count == 0) 1014 { 1015 // If supportedDevices is empty, all device layouts are considered supported. 1016 return true; 1017 } 1018 1019 for (var n = 0; n < supportedDevices.Count; ++n) 1020 { 1021 var supportedLayout = new InternedString(supportedDevices[n]); 1022 if (layoutName == supportedLayout || m_Layouts.IsBasedOn(supportedLayout, layoutName)) 1023 return true; 1024 } 1025 1026 return false; 1027 } 1028 1029 public IEnumerable<string> ListControlLayouts(string basedOn = null) 1030 { 1031 ////FIXME: this may add a name twice 1032 1033 if (!string.IsNullOrEmpty(basedOn)) 1034 { 1035 var internedBasedOn = new InternedString(basedOn); 1036 foreach (var entry in m_Layouts.layoutTypes) 1037 if (m_Layouts.IsBasedOn(internedBasedOn, entry.Key)) 1038 yield return entry.Key; 1039 foreach (var entry in m_Layouts.layoutStrings) 1040 if (m_Layouts.IsBasedOn(internedBasedOn, entry.Key)) 1041 yield return entry.Key; 1042 foreach (var entry in m_Layouts.layoutBuilders) 1043 if (m_Layouts.IsBasedOn(internedBasedOn, entry.Key)) 1044 yield return entry.Key; 1045 } 1046 else 1047 { 1048 foreach (var entry in m_Layouts.layoutTypes) 1049 yield return entry.Key; 1050 foreach (var entry in m_Layouts.layoutStrings) 1051 yield return entry.Key; 1052 foreach (var entry in m_Layouts.layoutBuilders) 1053 yield return entry.Key; 1054 } 1055 } 1056 1057 // Adds all controls that match the given path spec to the given list. 1058 // Returns number of controls added to the list. 1059 // NOTE: Does not create garbage. 1060 1061 /// <summary> 1062 /// Adds to the given list all controls that match the given <see cref="InputControlPath">path spec</see> 1063 /// and are assignable to the given type. 1064 /// </summary> 1065 /// <param name="path"></param> 1066 /// <param name="controls"></param> 1067 /// <typeparam name="TControl"></typeparam> 1068 /// <returns></returns> 1069 public int GetControls<TControl>(string path, ref InputControlList<TControl> controls) 1070 where TControl : InputControl 1071 { 1072 if (string.IsNullOrEmpty(path)) 1073 return 0; 1074 if (m_DevicesCount == 0) 1075 return 0; 1076 1077 var deviceCount = m_DevicesCount; 1078 var numMatches = 0; 1079 for (var i = 0; i < deviceCount; ++i) 1080 { 1081 var device = m_Devices[i]; 1082 numMatches += InputControlPath.TryFindControls(device, path, 0, ref controls); 1083 } 1084 1085 return numMatches; 1086 } 1087 1088 public void SetDeviceUsage(InputDevice device, InternedString usage) 1089 { 1090 if (device == null) 1091 throw new ArgumentNullException(nameof(device)); 1092 if (device.usages.Count == 1 && device.usages[0] == usage) 1093 return; 1094 if (device.usages.Count == 0 && usage.IsEmpty()) 1095 return; 1096 1097 device.ClearDeviceUsages(); 1098 if (!usage.IsEmpty()) 1099 device.AddDeviceUsage(usage); 1100 NotifyUsageChanged(device); 1101 } 1102 1103 public void AddDeviceUsage(InputDevice device, InternedString usage) 1104 { 1105 if (device == null) 1106 throw new ArgumentNullException(nameof(device)); 1107 if (usage.IsEmpty()) 1108 throw new ArgumentException("Usage string cannot be empty", nameof(usage)); 1109 if (device.usages.Contains(usage)) 1110 return; 1111 1112 device.AddDeviceUsage(usage); 1113 NotifyUsageChanged(device); 1114 } 1115 1116 public void RemoveDeviceUsage(InputDevice device, InternedString usage) 1117 { 1118 if (device == null) 1119 throw new ArgumentNullException(nameof(device)); 1120 if (usage.IsEmpty()) 1121 throw new ArgumentException("Usage string cannot be empty", nameof(usage)); 1122 if (!device.usages.Contains(usage)) 1123 return; 1124 1125 device.RemoveDeviceUsage(usage); 1126 NotifyUsageChanged(device); 1127 } 1128 1129 private void NotifyUsageChanged(InputDevice device) 1130 { 1131 InputActionState.OnDeviceChange(device, InputDeviceChange.UsageChanged); 1132 1133 // Notify listeners. 1134 DelegateHelpers.InvokeCallbacksSafe(ref m_DeviceChangeListeners, device, InputDeviceChange.UsageChanged, k_InputOnDeviceChangeMarker, "InputSystem.onDeviceChange"); 1135 1136 ////REVIEW: This was for the XRController leftHand and rightHand getters but these do lookups dynamically now; remove? 1137 // Usage may affect current device so update. 1138 device.MakeCurrent(); 1139 } 1140 1141 ////TODO: make sure that no device or control with a '/' in the name can creep into the system 1142 1143 public InputDevice AddDevice(Type type, string name = null) 1144 { 1145 if (type == null) 1146 throw new ArgumentNullException(nameof(type)); 1147 1148 // Find the layout name that the given type was registered with. 1149 var layoutName = FindOrRegisterDeviceLayoutForType(type); 1150 Debug.Assert(!layoutName.IsEmpty(), name); 1151 1152 // Note that since we go through the normal by-name lookup here, this will 1153 // still work if the layout from the type was override with a string layout. 1154 return AddDevice(layoutName, name); 1155 } 1156 1157 // Creates a device from the given layout and adds it to the system. 1158 // NOTE: Creates garbage. 1159 public InputDevice AddDevice(string layout, string name = null, InternedString variants = new InternedString()) 1160 { 1161 if (string.IsNullOrEmpty(layout)) 1162 throw new ArgumentNullException(nameof(layout)); 1163 1164 var device = InputDevice.Build<InputDevice>(layout, variants); 1165 1166 if (!string.IsNullOrEmpty(name)) 1167 device.m_Name = new InternedString(name); 1168 1169 AddDevice(device); 1170 1171 return device; 1172 } 1173 1174 // Add device with a forced ID. Used when creating devices reported to us by native. 1175 private InputDevice AddDevice(InternedString layout, int deviceId, 1176 string deviceName = null, 1177 InputDeviceDescription deviceDescription = new InputDeviceDescription(), 1178 InputDevice.DeviceFlags deviceFlags = 0, 1179 InternedString variants = default) 1180 { 1181 var device = InputDevice.Build<InputDevice>(new InternedString(layout), 1182 deviceDescription: deviceDescription, 1183 layoutVariants: variants); 1184 1185 device.m_DeviceId = deviceId; 1186 device.m_Description = deviceDescription; 1187 device.m_DeviceFlags |= deviceFlags; 1188 if (!string.IsNullOrEmpty(deviceName)) 1189 device.m_Name = new InternedString(deviceName); 1190 1191 // Default display name to product name. 1192 if (!string.IsNullOrEmpty(deviceDescription.product)) 1193 device.m_DisplayName = deviceDescription.product; 1194 1195 AddDevice(device); 1196 1197 return device; 1198 } 1199 1200 public void AddDevice(InputDevice device) 1201 { 1202 if (device == null) 1203 throw new ArgumentNullException(nameof(device)); 1204 if (string.IsNullOrEmpty(device.layout)) 1205 throw new InvalidOperationException("Device has no associated layout"); 1206 1207 // Ignore if the same device gets added multiple times. 1208 if (ArrayHelpers.Contains(m_Devices, device)) 1209 return; 1210 1211 MakeDeviceNameUnique(device); 1212 AssignUniqueDeviceId(device); 1213 1214 // Add to list. 1215 device.m_DeviceIndex = ArrayHelpers.AppendWithCapacity(ref m_Devices, ref m_DevicesCount, device); 1216 1217 ////REVIEW: Not sure a full-blown dictionary is the right way here. Alternatives are to keep 1218 //// a sparse array that directly indexes using the linearly increasing IDs (though that 1219 //// may get large over time). Or to just do a linear search through m_Devices (but 1220 //// that may end up tapping a bunch of memory locations in the heap to find the right 1221 //// device; could be improved by sorting m_Devices by ID and picking a good starting 1222 //// point based on the ID we have instead of searching from [0] always). 1223 m_DevicesById[device.deviceId] = device; 1224 1225 // Let InputStateBuffers know this device doesn't have any associated state yet. 1226 device.m_StateBlock.byteOffset = InputStateBlock.InvalidOffset; 1227 1228 // Update state buffers. 1229 ReallocateStateBuffers(); 1230 InitializeDeviceState(device); 1231 1232 // Update metrics. 1233 m_Metrics.maxNumDevices = Mathf.Max(m_DevicesCount, m_Metrics.maxNumDevices); 1234 m_Metrics.maxStateSizeInBytes = Mathf.Max((int)m_StateBuffers.totalSize, m_Metrics.maxStateSizeInBytes); 1235 1236 // Make sure that if the device ID is listed in m_AvailableDevices, the device 1237 // is no longer marked as removed. 1238 for (var i = 0; i < m_AvailableDeviceCount; ++i) 1239 { 1240 if (m_AvailableDevices[i].deviceId == device.deviceId) 1241 m_AvailableDevices[i].isRemoved = false; 1242 } 1243 1244 // If we're running in the background, find out whether the device can run in 1245 // the background. If not, disable it. 1246 var isPlaying = true; 1247 #if UNITY_EDITOR 1248 isPlaying = m_Runtime.isInPlayMode; 1249 #endif 1250 if (isPlaying && !gameHasFocus 1251 && m_Settings.backgroundBehavior != InputSettings.BackgroundBehavior.IgnoreFocus 1252 && m_Runtime.runInBackground 1253 && device.QueryEnabledStateFromRuntime() 1254 && !ShouldRunDeviceInBackground(device)) 1255 { 1256 EnableOrDisableDevice(device, false, DeviceDisableScope.TemporaryWhilePlayerIsInBackground); 1257 } 1258 1259 ////REVIEW: we may want to suppress this during the initial device discovery phase 1260 // Let actions re-resolve their paths. 1261 InputActionState.OnDeviceChange(device, InputDeviceChange.Added); 1262 1263 // If the device wants automatic callbacks before input updates, 1264 // put it on the list. 1265 if (device is IInputUpdateCallbackReceiver beforeUpdateCallbackReceiver) 1266 onBeforeUpdate += beforeUpdateCallbackReceiver.OnUpdate; 1267 1268 // If the device has state callbacks, make a note of it. 1269 if (device is IInputStateCallbackReceiver) 1270 { 1271 InstallBeforeUpdateHookIfNecessary(); 1272 device.m_DeviceFlags |= InputDevice.DeviceFlags.HasStateCallbacks; 1273 m_HaveDevicesWithStateCallbackReceivers = true; 1274 } 1275 1276 // If the device has event merger, make a note of it. 1277 if (device is IEventMerger) 1278 device.hasEventMerger = true; 1279 1280 // If the device has event preprocessor, make a note of it. 1281 if (device is IEventPreProcessor) 1282 device.hasEventPreProcessor = true; 1283 1284 // If the device wants before-render updates, enable them if they 1285 // aren't already. 1286 if (device.updateBeforeRender) 1287 updateMask |= InputUpdateType.BeforeRender; 1288 1289 // Notify device. 1290 device.NotifyAdded(); 1291 1292 ////REVIEW: is this really a good thing to do? just plugging in a device shouldn't make 1293 //// it current, no? 1294 // Make the device current. 1295 // BEWARE: if this will not happen for whatever reason, you will break Android sensors, 1296 // as they rely on .current for enabling native backend, see https://fogbugz.unity3d.com/f/cases/1371204/ 1297 device.MakeCurrent(); 1298 1299 // Notify listeners. 1300 DelegateHelpers.InvokeCallbacksSafe(ref m_DeviceChangeListeners, device, InputDeviceChange.Added, k_InputOnDeviceChangeMarker, "InputSystem.onDeviceChange"); 1301 1302 // Request device to send us an initial state update. 1303 if (device.enabled) 1304 device.RequestSync(); 1305 1306 device.SetOptimizedControlDataTypeRecursively(); 1307 } 1308 1309 ////TODO: this path should really put the device on the list of available devices 1310 ////TODO: this path should discover disconnected devices 1311 public InputDevice AddDevice(InputDeviceDescription description) 1312 { 1313 ////REVIEW: is throwing here really such a useful thing? 1314 return AddDevice(description, throwIfNoLayoutFound: true); 1315 } 1316 1317 public InputDevice AddDevice(InputDeviceDescription description, bool throwIfNoLayoutFound, 1318 string deviceName = null, int deviceId = InputDevice.InvalidDeviceId, InputDevice.DeviceFlags deviceFlags = 0) 1319 { 1320 k_InputAddDeviceMarker.Begin(); 1321 // Look for matching layout. 1322 var layout = TryFindMatchingControlLayout(ref description, deviceId); 1323 1324 // If no layout was found, bail out. 1325 if (layout.IsEmpty()) 1326 { 1327 if (throwIfNoLayoutFound) 1328 { 1329 k_InputAddDeviceMarker.End(); 1330 throw new ArgumentException($"Cannot find layout matching device description '{description}'", nameof(description)); 1331 } 1332 1333 // If it's a device coming from the runtime, disable it. 1334 if (deviceId != InputDevice.InvalidDeviceId) 1335 { 1336 var command = DisableDeviceCommand.Create(); 1337 m_Runtime.DeviceCommand(deviceId, ref command); 1338 } 1339 1340 k_InputAddDeviceMarker.End(); 1341 return null; 1342 } 1343 1344 var device = AddDevice(layout, deviceId, deviceName, description, deviceFlags); 1345 device.m_Description = description; 1346 k_InputAddDeviceMarker.End(); 1347 return device; 1348 } 1349 1350 public InputDevice AddDevice(InputDeviceDescription description, InternedString layout, string deviceName = null, 1351 int deviceId = InputDevice.InvalidDeviceId, InputDevice.DeviceFlags deviceFlags = 0) 1352 { 1353 try 1354 { 1355 k_InputAddDeviceMarker.Begin(); 1356 var device = AddDevice(layout, deviceId, deviceName, description, deviceFlags); 1357 device.m_Description = description; 1358 return device; 1359 } 1360 finally 1361 { 1362 k_InputAddDeviceMarker.End(); 1363 } 1364 } 1365 1366 public void RemoveDevice(InputDevice device, bool keepOnListOfAvailableDevices = false) 1367 { 1368 if (device == null) 1369 throw new ArgumentNullException(nameof(device)); 1370 1371 // If device has not been added, ignore. 1372 if (device.m_DeviceIndex == InputDevice.kInvalidDeviceIndex) 1373 return; 1374 1375 // Remove state monitors while device index is still valid. 1376 RemoveStateChangeMonitors(device); 1377 1378 // Remove from device array. 1379 var deviceIndex = device.m_DeviceIndex; 1380 var deviceId = device.deviceId; 1381 if (deviceIndex < m_StateChangeMonitors.LengthSafe()) 1382 { 1383 // m_StateChangeMonitors mirrors layout of m_Devices *but* may be shorter. 1384 var count = m_StateChangeMonitors.Length; 1385 ArrayHelpers.EraseAtWithCapacity(m_StateChangeMonitors, ref count, deviceIndex); 1386 } 1387 ArrayHelpers.EraseAtWithCapacity(m_Devices, ref m_DevicesCount, deviceIndex); 1388 1389 m_DevicesById.Remove(deviceId); 1390 1391 if (m_Devices != null) 1392 { 1393 // Remove from state buffers. 1394 ReallocateStateBuffers(); 1395 } 1396 else 1397 { 1398 // No more devices. Kill state buffers. 1399 m_StateBuffers.FreeAll(); 1400 } 1401 1402 ////TODO: When we remove a native device like this, make sure we tell the backend to disable it (and re-enable it when re-add it) 1403 1404 // Update device indices. Do this after reallocating state buffers as that call requires 1405 // the old indices to still be in place. 1406 for (var i = deviceIndex; i < m_DevicesCount; ++i) 1407 --m_Devices[i].m_DeviceIndex; // Indices have shifted down by one. 1408 device.m_DeviceIndex = InputDevice.kInvalidDeviceIndex; 1409 1410 // Update list of available devices. 1411 for (var i = 0; i < m_AvailableDeviceCount; ++i) 1412 { 1413 if (m_AvailableDevices[i].deviceId == deviceId) 1414 { 1415 if (keepOnListOfAvailableDevices) 1416 m_AvailableDevices[i].isRemoved = true; 1417 else 1418 ArrayHelpers.EraseAtWithCapacity(m_AvailableDevices, ref m_AvailableDeviceCount, i); 1419 break; 1420 } 1421 } 1422 1423 // Unbake offset into global state buffers. 1424 device.BakeOffsetIntoStateBlockRecursive((uint)-device.m_StateBlock.byteOffset); 1425 1426 // Force enabled actions to remove controls from the device. 1427 // We've already set the device index to be invalid so we any attempts 1428 // by actions to uninstall state monitors will get ignored. 1429 InputActionState.OnDeviceChange(device, InputDeviceChange.Removed); 1430 1431 // Kill before update callback, if applicable. 1432 if (device is IInputUpdateCallbackReceiver beforeUpdateCallbackReceiver) 1433 onBeforeUpdate -= beforeUpdateCallbackReceiver.OnUpdate; 1434 1435 // Disable before-render updates if this was the last device 1436 // that requires them. 1437 if (device.updateBeforeRender) 1438 { 1439 var haveDeviceRequiringBeforeRender = false; 1440 for (var i = 0; i < m_DevicesCount; ++i) 1441 if (m_Devices[i].updateBeforeRender) 1442 { 1443 haveDeviceRequiringBeforeRender = true; 1444 break; 1445 } 1446 1447 if (!haveDeviceRequiringBeforeRender) 1448 updateMask &= ~InputUpdateType.BeforeRender; 1449 } 1450 1451 // Let device know. 1452 device.NotifyRemoved(); 1453 1454 // Let listeners know. 1455 DelegateHelpers.InvokeCallbacksSafe(ref m_DeviceChangeListeners, device, InputDeviceChange.Removed, k_InputOnDeviceChangeMarker, "InputSystem.onDeviceChange"); 1456 1457 // Try setting next device of same type as current 1458 InputSystem.GetDevice(device.GetType())?.MakeCurrent(); 1459 } 1460 1461 public void FlushDisconnectedDevices() 1462 { 1463 m_DisconnectedDevices.Clear(m_DisconnectedDevicesCount); 1464 m_DisconnectedDevicesCount = 0; 1465 } 1466 1467 public unsafe void ResetDevice(InputDevice device, bool alsoResetDontResetControls = false, bool? issueResetCommand = null) 1468 { 1469 if (device == null) 1470 throw new ArgumentNullException(nameof(device)); 1471 if (!device.added) 1472 throw new InvalidOperationException($"Device '{device}' has not been added to the system"); 1473 1474 var isHardReset = alsoResetDontResetControls || !device.hasDontResetControls; 1475 1476 // Trigger reset notification. 1477 var change = isHardReset ? InputDeviceChange.HardReset : InputDeviceChange.SoftReset; 1478 InputActionState.OnDeviceChange(device, change); 1479 DelegateHelpers.InvokeCallbacksSafe(ref m_DeviceChangeListeners, device, change, k_InputOnDeviceChangeMarker, "InputSystem.onDeviceChange"); 1480 1481 // If the device implements its own reset, let it handle it. 1482 if (!alsoResetDontResetControls && device is ICustomDeviceReset customReset) 1483 { 1484 customReset.Reset(); 1485 } 1486 else 1487 { 1488 var defaultStatePtr = device.defaultStatePtr; 1489 var deviceStateBlockSize = device.stateBlock.alignedSizeInBytes; 1490 1491 // Allocate temp memory to hold one state event. 1492 ////REVIEW: the need for an event here is sufficiently obscure to warrant scrutiny; likely, there's a better way 1493 //// to tell synthetic input (or input sources in general) apart 1494 // NOTE: We wrap the reset in an artificial state event so that it appears to the rest of the system 1495 // like any other input. If we don't do that but rather just call UpdateState() with a null event 1496 // pointer, the change will be considered an internal state change and will get ignored by some 1497 // pieces of code (such as EnhancedTouch which filters out internal state changes of Touchscreen 1498 // by ignoring any change that is not coming from an input event). 1499 using (var tempBuffer = 1500 new NativeArray<byte>(InputEvent.kBaseEventSize + sizeof(int) + (int)deviceStateBlockSize, Allocator.Temp)) 1501 { 1502 var stateEventPtr = (StateEvent*)tempBuffer.GetUnsafePtr(); 1503 var statePtr = stateEventPtr->state; 1504 var currentTime = m_Runtime.currentTime; 1505 1506 // Set up the state event. 1507 ref var stateBlock = ref device.m_StateBlock; 1508 stateEventPtr->baseEvent.type = StateEvent.Type; 1509 stateEventPtr->baseEvent.sizeInBytes = InputEvent.kBaseEventSize + sizeof(int) + deviceStateBlockSize; 1510 stateEventPtr->baseEvent.time = currentTime; 1511 stateEventPtr->baseEvent.deviceId = device.deviceId; 1512 stateEventPtr->baseEvent.eventId = -1; 1513 stateEventPtr->stateFormat = device.m_StateBlock.format; 1514 1515 // Decide whether we perform a soft reset or a hard reset. 1516 if (isHardReset) 1517 { 1518 // Perform a hard reset where we wipe the entire device and set a full 1519 // reset request to the backend. 1520 UnsafeUtility.MemCpy(statePtr, 1521 (byte*)defaultStatePtr + stateBlock.byteOffset, 1522 deviceStateBlockSize); 1523 } 1524 else 1525 { 1526 // Perform a soft reset where we exclude any dontReset control (which is automatically 1527 // toggled on for noisy controls) and do *NOT* send a reset request to the backend. 1528 1529 var currentStatePtr = device.currentStatePtr; 1530 var resetMaskPtr = m_StateBuffers.resetMaskBuffer; 1531 1532 // To preserve values from dontReset controls, we need to first copy their current values. 1533 UnsafeUtility.MemCpy(statePtr, 1534 (byte*)currentStatePtr + stateBlock.byteOffset, 1535 deviceStateBlockSize); 1536 1537 // And then we copy over default values masked by dontReset bits. 1538 MemoryHelpers.MemCpyMasked(statePtr, 1539 (byte*)defaultStatePtr + stateBlock.byteOffset, 1540 (int)deviceStateBlockSize, 1541 (byte*)resetMaskPtr + stateBlock.byteOffset); 1542 } 1543 1544 UpdateState(device, defaultUpdateType, statePtr, 0, deviceStateBlockSize, currentTime, 1545 new InputEventPtr((InputEvent*)stateEventPtr)); 1546 } 1547 } 1548 1549 // In the editor, we don't want to issue RequestResetCommand to devices based on focus of the game view 1550 // as this would also reset device state for the editor. And we don't need the reset commands in this case 1551 // as -- unlike in the player --, Unity keeps running and we will keep seeing OS messages for these devices. 1552 // So, in the editor, we generally suppress reset commands. 1553 // 1554 // The only exception is when the editor itself loses focus. We issue sync requests to all devices when 1555 // coming back into focus. But for any device that doesn't support syncs, we actually do want to have a 1556 // reset command reach the background. 1557 // 1558 // Finally, in the player, we also avoid reset commands when disabling a device as these are pointless. 1559 // We sync/reset when enabling a device in the backend. 1560 var doIssueResetCommand = isHardReset; 1561 if (issueResetCommand != null) 1562 doIssueResetCommand = issueResetCommand.Value; 1563 #if UNITY_EDITOR 1564 else if (m_Settings.editorInputBehaviorInPlayMode != InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView) 1565 doIssueResetCommand = false; 1566 #endif 1567 1568 if (doIssueResetCommand) 1569 device.RequestReset(); 1570 } 1571 1572 public InputDevice TryGetDevice(string nameOrLayout) 1573 { 1574 if (string.IsNullOrEmpty(nameOrLayout)) 1575 throw new ArgumentException("Name is null or empty.", nameof(nameOrLayout)); 1576 1577 if (m_DevicesCount == 0) 1578 return null; 1579 1580 var nameOrLayoutLowerCase = nameOrLayout.ToLower(); 1581 1582 for (var i = 0; i < m_DevicesCount; ++i) 1583 { 1584 var device = m_Devices[i]; 1585 if (device.m_Name.ToLower() == nameOrLayoutLowerCase || 1586 device.m_Layout.ToLower() == nameOrLayoutLowerCase) 1587 return device; 1588 } 1589 1590 return null; 1591 } 1592 1593 public InputDevice GetDevice(string nameOrLayout) 1594 { 1595 var device = TryGetDevice(nameOrLayout); 1596 if (device == null) 1597 throw new ArgumentException($"Cannot find device with name or layout '{nameOrLayout}'", nameof(nameOrLayout)); 1598 1599 return device; 1600 } 1601 1602 public InputDevice TryGetDevice(Type layoutType) 1603 { 1604 var layoutName = m_Layouts.TryFindLayoutForType(layoutType); 1605 if (layoutName.IsEmpty()) 1606 return null; 1607 1608 return TryGetDevice(layoutName); 1609 } 1610 1611 public InputDevice TryGetDeviceById(int id) 1612 { 1613 if (m_DevicesById.TryGetValue(id, out var result)) 1614 return result; 1615 return null; 1616 } 1617 1618 // Adds any device that's been reported to the system but could not be matched to 1619 // a layout to the given list. 1620 public int GetUnsupportedDevices(List<InputDeviceDescription> descriptions) 1621 { 1622 if (descriptions == null) 1623 throw new ArgumentNullException(nameof(descriptions)); 1624 1625 var numFound = 0; 1626 for (var i = 0; i < m_AvailableDeviceCount; ++i) 1627 { 1628 if (TryGetDeviceById(m_AvailableDevices[i].deviceId) != null) 1629 continue; 1630 1631 descriptions.Add(m_AvailableDevices[i].description); 1632 ++numFound; 1633 } 1634 1635 return numFound; 1636 } 1637 1638 // We have three different levels of disabling a device. 1639 internal enum DeviceDisableScope 1640 { 1641 Everywhere, // Device is disabled globally and explicitly. Should neither send nor receive events. 1642 InFrontendOnly, // Device is only disabled on managed side but not in backend. Should keep sending events but should not receive them (useful for redirecting their data). 1643 TemporaryWhilePlayerIsInBackground, // Device has been disabled automatically and temporarily by system while application is running in the background. 1644 } 1645 1646 public void EnableOrDisableDevice(InputDevice device, bool enable, DeviceDisableScope scope = default) 1647 { 1648 if (device == null) 1649 throw new ArgumentNullException(nameof(device)); 1650 1651 // Synchronize the enable/disabled state of the device. 1652 if (enable) 1653 { 1654 ////REVIEW: Do we really want to allow overriding disabledWhileInBackground like it currently does? 1655 1656 // Enable device. 1657 switch (scope) 1658 { 1659 case DeviceDisableScope.Everywhere: 1660 device.disabledWhileInBackground = false; 1661 if (!device.disabledInFrontend && !device.disabledInRuntime) 1662 return; 1663 if (device.disabledInRuntime) 1664 { 1665 device.ExecuteEnableCommand(); 1666 device.disabledInRuntime = false; 1667 } 1668 if (device.disabledInFrontend) 1669 { 1670 if (!device.RequestSync()) 1671 ResetDevice(device); 1672 device.disabledInFrontend = false; 1673 } 1674 break; 1675 1676 case DeviceDisableScope.InFrontendOnly: 1677 device.disabledWhileInBackground = false; 1678 if (!device.disabledInFrontend && device.disabledInRuntime) 1679 return; 1680 if (!device.disabledInRuntime) 1681 { 1682 device.ExecuteDisableCommand(); 1683 device.disabledInRuntime = true; 1684 } 1685 if (device.disabledInFrontend) 1686 { 1687 if (!device.RequestSync()) 1688 ResetDevice(device); 1689 device.disabledInFrontend = false; 1690 } 1691 break; 1692 1693 case DeviceDisableScope.TemporaryWhilePlayerIsInBackground: 1694 if (device.disabledWhileInBackground) 1695 { 1696 if (device.disabledInRuntime) 1697 { 1698 device.ExecuteEnableCommand(); 1699 device.disabledInRuntime = false; 1700 } 1701 if (!device.RequestSync()) 1702 ResetDevice(device); 1703 device.disabledWhileInBackground = false; 1704 } 1705 break; 1706 } 1707 } 1708 else 1709 { 1710 // Disable device. 1711 switch (scope) 1712 { 1713 case DeviceDisableScope.Everywhere: 1714 device.disabledWhileInBackground = false; 1715 if (device.disabledInFrontend && device.disabledInRuntime) 1716 return; 1717 if (!device.disabledInRuntime) 1718 { 1719 device.ExecuteDisableCommand(); 1720 device.disabledInRuntime = true; 1721 } 1722 if (!device.disabledInFrontend) 1723 { 1724 // When disabling a device, also issuing a reset in the backend is pointless. 1725 ResetDevice(device, issueResetCommand: false); 1726 device.disabledInFrontend = true; 1727 } 1728 break; 1729 1730 case DeviceDisableScope.InFrontendOnly: 1731 device.disabledWhileInBackground = false; 1732 if (!device.disabledInRuntime && device.disabledInFrontend) 1733 return; 1734 if (device.disabledInRuntime) 1735 { 1736 device.ExecuteEnableCommand(); 1737 device.disabledInRuntime = false; 1738 } 1739 if (!device.disabledInFrontend) 1740 { 1741 // When disabling a device, also issuing a reset in the backend is pointless. 1742 ResetDevice(device, issueResetCommand: false); 1743 device.disabledInFrontend = true; 1744 } 1745 break; 1746 1747 case DeviceDisableScope.TemporaryWhilePlayerIsInBackground: 1748 // Won't flag a device as DisabledWhileInBackground if it is explicitly disabled in 1749 // the frontend. 1750 if (device.disabledInFrontend || device.disabledWhileInBackground) 1751 return; 1752 device.disabledWhileInBackground = true; 1753 ResetDevice(device, issueResetCommand: false); 1754 #if UNITY_EDITOR 1755 if (m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView) 1756 #endif 1757 { 1758 device.ExecuteDisableCommand(); 1759 device.disabledInRuntime = true; 1760 } 1761 break; 1762 } 1763 } 1764 1765 // Let listeners know. 1766 var deviceChange = enable ? InputDeviceChange.Enabled : InputDeviceChange.Disabled; 1767 DelegateHelpers.InvokeCallbacksSafe(ref m_DeviceChangeListeners, device, deviceChange, k_InputOnDeviceChangeMarker, "InputSystem.onDeviceChange"); 1768 } 1769 1770 private unsafe void QueueEvent(InputEvent* eventPtr) 1771 { 1772 // If we're currently in OnUpdate(), the m_InputEventStream will be open. In that case, 1773 // append events directly to that buffer and do *NOT* go into native. 1774 if (m_InputEventStream.isOpen) 1775 { 1776 m_InputEventStream.Write(eventPtr); 1777 return; 1778 } 1779 1780 // Don't bother keeping the data on the managed side. Just stuff the raw data directly 1781 // into the native buffers. This also means this method is thread-safe. 1782 m_Runtime.QueueEvent(eventPtr); 1783 } 1784 1785 public unsafe void QueueEvent(InputEventPtr ptr) 1786 { 1787 QueueEvent(ptr.data); 1788 } 1789 1790 public unsafe void QueueEvent<TEvent>(ref TEvent inputEvent) 1791 where TEvent : struct, IInputEventTypeInfo 1792 { 1793 QueueEvent((InputEvent*)UnsafeUtility.AddressOf(ref inputEvent)); 1794 } 1795 1796 public void Update() 1797 { 1798 Update(defaultUpdateType); 1799 } 1800 1801 public void Update(InputUpdateType updateType) 1802 { 1803 m_Runtime.Update(updateType); 1804 } 1805 1806 internal void Initialize(IInputRuntime runtime, InputSettings settings) 1807 { 1808 Debug.Assert(settings != null); 1809 1810 m_Settings = settings; 1811 1812#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 1813 InitializeActions(); 1814#endif // UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 1815 InitializeData(); 1816 InstallRuntime(runtime); 1817 InstallGlobals(); 1818 1819 ApplySettings(); 1820 #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 1821 ApplyActions(); 1822 #endif 1823 } 1824 1825 internal void Destroy() 1826 { 1827 // There isn't really much of a point in removing devices but we still 1828 // want to clear out any global state they may be keeping. So just tell 1829 // the devices that they got removed without actually removing them. 1830 for (var i = 0; i < m_DevicesCount; ++i) 1831 m_Devices[i].NotifyRemoved(); 1832 1833 // Free all state memory. 1834 m_StateBuffers.FreeAll(); 1835 1836 // Uninstall globals. 1837 UninstallGlobals(); 1838 1839 // Destroy settings if they are temporary. 1840 if (m_Settings != null && m_Settings.hideFlags == HideFlags.HideAndDontSave) 1841 Object.DestroyImmediate(m_Settings); 1842 1843 // Project-wide Actions are never temporary so we do not destroy them. 1844 } 1845 1846#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 1847 // Initialize project-wide actions: 1848 // - In editor (edit mode or play-mode) we always use the editor build preferences persisted setting. 1849 // - In player build we always attempt to find a preloaded asset. 1850 private void InitializeActions() 1851 { 1852#if UNITY_EDITOR 1853 m_Actions = ProjectWideActionsBuildProvider.actionsToIncludeInPlayerBuild; 1854#else 1855 m_Actions = null; 1856 var candidates = Resources.FindObjectsOfTypeAll<InputActionAsset>(); 1857 foreach (var candidate in candidates) 1858 { 1859 if (candidate.m_IsProjectWide) 1860 { 1861 m_Actions = candidate; 1862 break; 1863 } 1864 } 1865#endif // UNITY_EDITOR 1866 } 1867 1868#endif // UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 1869 1870 internal void InitializeData() 1871 { 1872 m_Layouts.Allocate(); 1873 m_Processors.Initialize(); 1874 m_Interactions.Initialize(); 1875 m_Composites.Initialize(); 1876 m_DevicesById = new Dictionary<int, InputDevice>(); 1877 1878 // Determine our default set of enabled update types. By 1879 // default we enable both fixed and dynamic update because 1880 // we don't know which one the user is going to use. The user 1881 // can manually turn off one of them to optimize operation. 1882 m_UpdateMask = InputUpdateType.Dynamic | InputUpdateType.Fixed; 1883 m_HasFocus = Application.isFocused; 1884#if UNITY_EDITOR 1885 m_EditorIsActive = true; 1886 m_UpdateMask |= InputUpdateType.Editor; 1887#endif 1888 1889 m_ScrollDeltaBehavior = InputSettings.ScrollDeltaBehavior.UniformAcrossAllPlatforms; 1890 1891 // Default polling frequency is 60 Hz. 1892 m_PollingFrequency = 60; 1893 1894 // Register layouts. 1895 // NOTE: Base layouts must be registered before their derived layouts 1896 // for the detection of base layouts to work. 1897 RegisterControlLayout("Axis", typeof(AxisControl)); // Controls. 1898 RegisterControlLayout("Button", typeof(ButtonControl)); 1899 RegisterControlLayout("DiscreteButton", typeof(DiscreteButtonControl)); 1900 RegisterControlLayout("Key", typeof(KeyControl)); 1901 RegisterControlLayout("Analog", typeof(AxisControl)); 1902 RegisterControlLayout("Integer", typeof(IntegerControl)); 1903 RegisterControlLayout("Digital", typeof(IntegerControl)); 1904 RegisterControlLayout("Double", typeof(DoubleControl)); 1905 RegisterControlLayout("Vector2", typeof(Vector2Control)); 1906 RegisterControlLayout("Vector3", typeof(Vector3Control)); 1907 RegisterControlLayout("Delta", typeof(DeltaControl)); 1908 RegisterControlLayout("Quaternion", typeof(QuaternionControl)); 1909 RegisterControlLayout("Stick", typeof(StickControl)); 1910 RegisterControlLayout("Dpad", typeof(DpadControl)); 1911 RegisterControlLayout("DpadAxis", typeof(DpadControl.DpadAxisControl)); 1912 RegisterControlLayout("AnyKey", typeof(AnyKeyControl)); 1913 RegisterControlLayout("Touch", typeof(TouchControl)); 1914 RegisterControlLayout("TouchPhase", typeof(TouchPhaseControl)); 1915 RegisterControlLayout("TouchPress", typeof(TouchPressControl)); 1916 1917 RegisterControlLayout("Gamepad", typeof(Gamepad)); // Devices. 1918 RegisterControlLayout("Joystick", typeof(Joystick)); 1919 RegisterControlLayout("Keyboard", typeof(Keyboard)); 1920 RegisterControlLayout("Pointer", typeof(Pointer)); 1921 RegisterControlLayout("Mouse", typeof(Mouse)); 1922 RegisterControlLayout("Pen", typeof(Pen)); 1923 RegisterControlLayout("Touchscreen", typeof(Touchscreen)); 1924 RegisterControlLayout("Sensor", typeof(Sensor)); 1925 RegisterControlLayout("Accelerometer", typeof(Accelerometer)); 1926 RegisterControlLayout("Gyroscope", typeof(Gyroscope)); 1927 RegisterControlLayout("GravitySensor", typeof(GravitySensor)); 1928 RegisterControlLayout("AttitudeSensor", typeof(AttitudeSensor)); 1929 RegisterControlLayout("LinearAccelerationSensor", typeof(LinearAccelerationSensor)); 1930 RegisterControlLayout("MagneticFieldSensor", typeof(MagneticFieldSensor)); 1931 RegisterControlLayout("LightSensor", typeof(LightSensor)); 1932 RegisterControlLayout("PressureSensor", typeof(PressureSensor)); 1933 RegisterControlLayout("HumiditySensor", typeof(HumiditySensor)); 1934 RegisterControlLayout("AmbientTemperatureSensor", typeof(AmbientTemperatureSensor)); 1935 RegisterControlLayout("StepCounter", typeof(StepCounter)); 1936 RegisterControlLayout("TrackedDevice", typeof(TrackedDevice)); 1937 1938 // Precompiled layouts. 1939 RegisterPrecompiledLayout<FastKeyboard>(FastKeyboard.metadata); 1940 RegisterPrecompiledLayout<FastTouchscreen>(FastTouchscreen.metadata); 1941 RegisterPrecompiledLayout<FastMouse>(FastMouse.metadata); 1942 1943 // Register processors. 1944 processors.AddTypeRegistration("Invert", typeof(InvertProcessor)); 1945 processors.AddTypeRegistration("InvertVector2", typeof(InvertVector2Processor)); 1946 processors.AddTypeRegistration("InvertVector3", typeof(InvertVector3Processor)); 1947 processors.AddTypeRegistration("Clamp", typeof(ClampProcessor)); 1948 processors.AddTypeRegistration("Normalize", typeof(NormalizeProcessor)); 1949 processors.AddTypeRegistration("NormalizeVector2", typeof(NormalizeVector2Processor)); 1950 processors.AddTypeRegistration("NormalizeVector3", typeof(NormalizeVector3Processor)); 1951 processors.AddTypeRegistration("Scale", typeof(ScaleProcessor)); 1952 processors.AddTypeRegistration("ScaleVector2", typeof(ScaleVector2Processor)); 1953 processors.AddTypeRegistration("ScaleVector3", typeof(ScaleVector3Processor)); 1954 processors.AddTypeRegistration("StickDeadzone", typeof(StickDeadzoneProcessor)); 1955 processors.AddTypeRegistration("AxisDeadzone", typeof(AxisDeadzoneProcessor)); 1956 processors.AddTypeRegistration("CompensateDirection", typeof(CompensateDirectionProcessor)); 1957 processors.AddTypeRegistration("CompensateRotation", typeof(CompensateRotationProcessor)); 1958 1959 #if UNITY_EDITOR 1960 processors.AddTypeRegistration("AutoWindowSpace", typeof(EditorWindowSpaceProcessor)); 1961 #endif 1962 1963 // Register interactions. 1964 interactions.AddTypeRegistration("Hold", typeof(HoldInteraction)); 1965 interactions.AddTypeRegistration("Tap", typeof(TapInteraction)); 1966 interactions.AddTypeRegistration("SlowTap", typeof(SlowTapInteraction)); 1967 interactions.AddTypeRegistration("MultiTap", typeof(MultiTapInteraction)); 1968 interactions.AddTypeRegistration("Press", typeof(PressInteraction)); 1969 1970 // Register composites. 1971 composites.AddTypeRegistration("1DAxis", typeof(AxisComposite)); 1972 composites.AddTypeRegistration("2DVector", typeof(Vector2Composite)); 1973 composites.AddTypeRegistration("3DVector", typeof(Vector3Composite)); 1974 composites.AddTypeRegistration("Axis", typeof(AxisComposite));// Alias for pre-0.2 name. 1975 composites.AddTypeRegistration("Dpad", typeof(Vector2Composite));// Alias for pre-0.2 name. 1976 composites.AddTypeRegistration("ButtonWithOneModifier", typeof(ButtonWithOneModifier)); 1977 composites.AddTypeRegistration("ButtonWithTwoModifiers", typeof(ButtonWithTwoModifiers)); 1978 composites.AddTypeRegistration("OneModifier", typeof(OneModifierComposite)); 1979 composites.AddTypeRegistration("TwoModifiers", typeof(TwoModifiersComposite)); 1980 1981 // Register custom types by reflection 1982 RegisterCustomTypes(); 1983 } 1984 1985 void RegisterCustomTypes(Type[] types) 1986 { 1987 foreach (Type type in types) 1988 { 1989 if (!type.IsClass 1990 || type.IsAbstract 1991 || type.IsGenericType) 1992 continue; 1993 if (typeof(InputProcessor).IsAssignableFrom(type)) 1994 { 1995 InputSystem.RegisterProcessor(type); 1996 } 1997 else if (typeof(IInputInteraction).IsAssignableFrom(type)) 1998 { 1999 InputSystem.RegisterInteraction(type); 2000 } 2001 else if (typeof(InputBindingComposite).IsAssignableFrom(type)) 2002 { 2003 InputSystem.RegisterBindingComposite(type, null); 2004 } 2005 } 2006 } 2007 2008 void RegisterCustomTypes() 2009 { 2010 k_InputRegisterCustomTypesMarker.Begin(); 2011 2012 var inputSystemAssembly = typeof(InputProcessor).Assembly; 2013 var inputSystemName = inputSystemAssembly.GetName().Name; 2014 var assemblies = AppDomain.CurrentDomain.GetAssemblies(); 2015 foreach (var assembly in assemblies) 2016 { 2017 try 2018 { 2019 // exclude InputSystem assembly which should be loaded first 2020 if (assembly == inputSystemAssembly) continue; 2021 2022 // Only register types from assemblies that reference InputSystem 2023 foreach (var referencedAssembly in assembly.GetReferencedAssemblies()) 2024 { 2025 if (referencedAssembly.Name == inputSystemName) 2026 { 2027 RegisterCustomTypes(assembly.GetTypes()); 2028 break; 2029 } 2030 } 2031 } 2032 catch (ReflectionTypeLoadException) 2033 { 2034 continue; 2035 } 2036 } 2037 2038 k_InputRegisterCustomTypesMarker.End(); 2039 } 2040 2041 internal void InstallRuntime(IInputRuntime runtime) 2042 { 2043 if (m_Runtime != null) 2044 { 2045 m_Runtime.onUpdate = null; 2046 m_Runtime.onBeforeUpdate = null; 2047 m_Runtime.onDeviceDiscovered = null; 2048 m_Runtime.onPlayerFocusChanged = null; 2049 m_Runtime.onShouldRunUpdate = null; 2050 #if UNITY_EDITOR 2051 m_Runtime.onPlayerLoopInitialization = null; 2052 #endif 2053 } 2054 2055 m_Runtime = runtime; 2056 m_Runtime.onUpdate = OnUpdate; 2057 m_Runtime.onDeviceDiscovered = OnNativeDeviceDiscovered; 2058 m_Runtime.onPlayerFocusChanged = OnFocusChanged; 2059 m_Runtime.onShouldRunUpdate = ShouldRunUpdate; 2060 #if UNITY_EDITOR 2061 m_Runtime.onPlayerLoopInitialization = OnPlayerLoopInitialization; 2062 #endif 2063 m_Runtime.pollingFrequency = pollingFrequency; 2064 m_HasFocus = m_Runtime.isPlayerFocused; 2065 2066 // We only hook NativeInputSystem.onBeforeUpdate if necessary. 2067 if (m_BeforeUpdateListeners.length > 0 || m_HaveDevicesWithStateCallbackReceivers) 2068 { 2069 m_Runtime.onBeforeUpdate = OnBeforeUpdate; 2070 m_NativeBeforeUpdateHooked = true; 2071 } 2072 2073 #if UNITY_ANALYTICS || UNITY_EDITOR 2074 InputAnalytics.Initialize(this); 2075 m_Runtime.onShutdown = () => InputAnalytics.OnShutdown(this); 2076 #endif 2077 } 2078 2079 internal void InstallGlobals() 2080 { 2081 Debug.Assert(m_Runtime != null); 2082 2083 InputControlLayout.s_Layouts = m_Layouts; 2084 InputProcessor.s_Processors = m_Processors; 2085 InputInteraction.s_Interactions = m_Interactions; 2086 InputBindingComposite.s_Composites = m_Composites; 2087 2088 InputRuntime.s_Instance = m_Runtime; 2089 InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup = 2090 m_Runtime.currentTimeOffsetToRealtimeSinceStartup; 2091 2092 // Reset update state. 2093 InputUpdate.Restore(new InputUpdate.SerializedState()); 2094 2095 unsafe 2096 { 2097 InputStateBuffers.SwitchTo(m_StateBuffers, InputUpdateType.Dynamic); 2098 InputStateBuffers.s_DefaultStateBuffer = m_StateBuffers.defaultStateBuffer; 2099 InputStateBuffers.s_NoiseMaskBuffer = m_StateBuffers.noiseMaskBuffer; 2100 InputStateBuffers.s_ResetMaskBuffer = m_StateBuffers.resetMaskBuffer; 2101 } 2102 } 2103 2104 internal void UninstallGlobals() 2105 { 2106 if (ReferenceEquals(InputControlLayout.s_Layouts.baseLayoutTable, m_Layouts.baseLayoutTable)) 2107 InputControlLayout.s_Layouts = new InputControlLayout.Collection(); 2108 if (ReferenceEquals(InputProcessor.s_Processors.table, m_Processors.table)) 2109 InputProcessor.s_Processors = new TypeTable(); 2110 if (ReferenceEquals(InputInteraction.s_Interactions.table, m_Interactions.table)) 2111 InputInteraction.s_Interactions = new TypeTable(); 2112 if (ReferenceEquals(InputBindingComposite.s_Composites.table, m_Composites.table)) 2113 InputBindingComposite.s_Composites = new TypeTable(); 2114 2115 // Clear layout cache. 2116 InputControlLayout.s_CacheInstance = default; 2117 InputControlLayout.s_CacheInstanceRef = 0; 2118 2119 // Detach from runtime. 2120 if (m_Runtime != null) 2121 { 2122 m_Runtime.onUpdate = null; 2123 m_Runtime.onDeviceDiscovered = null; 2124 m_Runtime.onBeforeUpdate = null; 2125 m_Runtime.onPlayerFocusChanged = null; 2126 m_Runtime.onShouldRunUpdate = null; 2127 2128 if (ReferenceEquals(InputRuntime.s_Instance, m_Runtime)) 2129 InputRuntime.s_Instance = null; 2130 } 2131 } 2132 2133 [Serializable] 2134 internal struct AvailableDevice 2135 { 2136 public InputDeviceDescription description; 2137 public int deviceId; 2138 public bool isNative; 2139 public bool isRemoved; 2140 } 2141 2142 // Used by EditorInputControlLayoutCache to determine whether its state is outdated. 2143 internal int m_LayoutRegistrationVersion; 2144 private float m_PollingFrequency; 2145 2146 internal InputControlLayout.Collection m_Layouts; 2147 private TypeTable m_Processors; 2148 private TypeTable m_Interactions; 2149 private TypeTable m_Composites; 2150 2151 private int m_DevicesCount; 2152 private InputDevice[] m_Devices; 2153 2154 private Dictionary<int, InputDevice> m_DevicesById; 2155 internal int m_AvailableDeviceCount; 2156 internal AvailableDevice[] m_AvailableDevices; // A record of all devices reported to the system (from native or user code). 2157 2158 ////REVIEW: should these be weak-referenced? 2159 internal int m_DisconnectedDevicesCount; 2160 internal InputDevice[] m_DisconnectedDevices; 2161 2162 internal InputUpdateType m_UpdateMask; // Which of our update types are enabled. 2163 private InputUpdateType m_CurrentUpdate; 2164 internal InputStateBuffers m_StateBuffers; 2165 2166 private InputSettings.ScrollDeltaBehavior m_ScrollDeltaBehavior; 2167 2168 #if UNITY_EDITOR 2169 // remember time offset to correctly restore it after editor mode is done 2170 private double latestNonEditorTimeOffsetToRealtimeSinceStartup; 2171 #endif 2172 2173 // We don't use UnityEvents and thus don't persist the callbacks during domain reloads. 2174 // Restoration of UnityActions is unreliable and it's too easy to end up with double 2175 // registrations what will lead to all kinds of misbehavior. 2176 private CallbackArray<DeviceChangeListener> m_DeviceChangeListeners; 2177 private CallbackArray<DeviceStateChangeListener> m_DeviceStateChangeListeners; 2178 private CallbackArray<InputDeviceFindControlLayoutDelegate> m_DeviceFindLayoutCallbacks; 2179 internal CallbackArray<InputDeviceCommandDelegate> m_DeviceCommandCallbacks; 2180 private CallbackArray<LayoutChangeListener> m_LayoutChangeListeners; 2181 private CallbackArray<EventListener> m_EventListeners; 2182 private CallbackArray<UpdateListener> m_BeforeUpdateListeners; 2183 private CallbackArray<UpdateListener> m_AfterUpdateListeners; 2184 private CallbackArray<Action> m_SettingsChangedListeners; 2185 #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 2186 private CallbackArray<Action> m_ActionsChangedListeners; 2187 #endif 2188 private bool m_NativeBeforeUpdateHooked; 2189 private bool m_HaveDevicesWithStateCallbackReceivers; 2190 private bool m_HasFocus; 2191 private InputEventStream m_InputEventStream; 2192 2193 // We want to sync devices when the editor comes back into focus. Unfortunately, there's no 2194 // callback for this so we have to poll this state. 2195 #if UNITY_EDITOR 2196 private bool m_EditorIsActive; 2197 #endif 2198 2199 // Allow external users to hook in validators and draw custom UI in the binding path editor 2200 #if UNITY_EDITOR 2201 private Utilities.CallbackArray<CustomBindingPathValidator> m_customBindingPathValidators; 2202 #endif 2203 2204 // We allocate the 'executeDeviceCommand' closure passed to 'onFindLayoutForDevice' 2205 // only once to avoid creating garbage. 2206 private InputDeviceExecuteCommandDelegate m_DeviceFindExecuteCommandDelegate; 2207 private int m_DeviceFindExecuteCommandDeviceId; 2208 2209 #if UNITY_ANALYTICS || UNITY_EDITOR 2210 private bool m_HaveSentStartupAnalytics; 2211 #endif 2212 2213 internal IInputRuntime m_Runtime; 2214 internal InputMetrics m_Metrics; 2215 internal InputSettings m_Settings; 2216 2217 // Extract as booleans (from m_Settings) because feature check is in the hot path 2218 2219 private bool m_OptimizedControlsFeatureEnabled; 2220 internal bool optimizedControlsFeatureEnabled 2221 { 2222 [MethodImpl(MethodImplOptions.AggressiveInlining)] 2223 get => m_OptimizedControlsFeatureEnabled; 2224 set => m_OptimizedControlsFeatureEnabled = value; 2225 } 2226 2227 private bool m_ReadValueCachingFeatureEnabled; 2228 internal bool readValueCachingFeatureEnabled 2229 { 2230 [MethodImpl(MethodImplOptions.AggressiveInlining)] 2231 get => m_ReadValueCachingFeatureEnabled; 2232 set => m_ReadValueCachingFeatureEnabled = value; 2233 } 2234 2235 private bool m_ParanoidReadValueCachingChecksEnabled; 2236 internal bool paranoidReadValueCachingChecksEnabled 2237 { 2238 [MethodImpl(MethodImplOptions.AggressiveInlining)] 2239 get => m_ParanoidReadValueCachingChecksEnabled; 2240 set => m_ParanoidReadValueCachingChecksEnabled = value; 2241 } 2242 2243 #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 2244 private InputActionAsset m_Actions; 2245 #endif 2246 2247 #if UNITY_EDITOR 2248 internal IInputDiagnostics m_Diagnostics; 2249 #endif 2250 2251 ////REVIEW: Make it so that device names *always* have a number appended? (i.e. Gamepad1, Gamepad2, etc. instead of Gamepad, Gamepad1, etc) 2252 2253 private void MakeDeviceNameUnique(InputDevice device) 2254 { 2255 if (m_DevicesCount == 0) 2256 return; 2257 2258 var deviceName = StringHelpers.MakeUniqueName(device.name, m_Devices, x => x != null ? x.name : string.Empty); 2259 if (deviceName != device.name) 2260 { 2261 // If we have changed the name of the device, nuke all path strings in the control 2262 // hierarchy so that they will get re-recreated when queried. 2263 ResetControlPathsRecursive(device); 2264 2265 // Assign name. 2266 device.m_Name = new InternedString(deviceName); 2267 } 2268 } 2269 2270 private static void ResetControlPathsRecursive(InputControl control) 2271 { 2272 control.m_Path = null; 2273 2274 var children = control.children; 2275 var childCount = children.Count; 2276 2277 for (var i = 0; i < childCount; ++i) 2278 ResetControlPathsRecursive(children[i]); 2279 } 2280 2281 private void AssignUniqueDeviceId(InputDevice device) 2282 { 2283 // If the device already has an ID, make sure it's unique. 2284 if (device.deviceId != InputDevice.InvalidDeviceId) 2285 { 2286 // Safety check to make sure out IDs are really unique. 2287 // Given they are assigned by the native system they should be fine 2288 // but let's make sure. 2289 var existingDeviceWithId = TryGetDeviceById(device.deviceId); 2290 if (existingDeviceWithId != null) 2291 throw new InvalidOperationException( 2292 $"Duplicate device ID {device.deviceId} detected for devices '{device.name}' and '{existingDeviceWithId.name}'"); 2293 } 2294 else 2295 { 2296 device.m_DeviceId = m_Runtime.AllocateDeviceId(); 2297 } 2298 } 2299 2300 // (Re)allocates state buffers and assigns each device that's been added 2301 // a segment of the buffer. Preserves the current state of devices. 2302 // NOTE: Installs the buffers globally. 2303 private unsafe void ReallocateStateBuffers() 2304 { 2305 var oldBuffers = m_StateBuffers; 2306 2307 // Allocate new buffers. 2308 var newBuffers = new InputStateBuffers(); 2309 newBuffers.AllocateAll(m_Devices, m_DevicesCount); 2310 2311 // Migrate state. 2312 newBuffers.MigrateAll(m_Devices, m_DevicesCount, oldBuffers); 2313 2314 // Install the new buffers. 2315 oldBuffers.FreeAll(); 2316 m_StateBuffers = newBuffers; 2317 InputStateBuffers.s_DefaultStateBuffer = newBuffers.defaultStateBuffer; 2318 InputStateBuffers.s_NoiseMaskBuffer = newBuffers.noiseMaskBuffer; 2319 InputStateBuffers.s_ResetMaskBuffer = newBuffers.resetMaskBuffer; 2320 2321 // Switch to buffers. 2322 InputStateBuffers.SwitchTo(m_StateBuffers, 2323 InputUpdate.s_LatestUpdateType != InputUpdateType.None ? InputUpdate.s_LatestUpdateType : defaultUpdateType); 2324 2325 ////TODO: need to update state change monitors 2326 } 2327 2328 /// <summary> 2329 /// Initialize default state for given device. 2330 /// </summary> 2331 /// <param name="device">A newly added input device.</param> 2332 /// <remarks> 2333 /// For every device, one copy of its state is kept around which is initialized with the default 2334 /// values for the device. If the device has no control that has an explicitly specified control 2335 /// value, the buffer simply contains all zeroes. 2336 /// 2337 /// The default state buffer is initialized once when a device is added to the system and then 2338 /// migrated by <see cref="InputStateBuffers"/> like other device state and removed when the device 2339 /// is removed from the system. 2340 /// </remarks> 2341 private unsafe void InitializeDefaultState(InputDevice device) 2342 { 2343 // Nothing to do if device has a default state of all zeroes. 2344 if (!device.hasControlsWithDefaultState) 2345 return; 2346 2347 // Otherwise go through each control and write its default value. 2348 var controls = device.allControls; 2349 var controlCount = controls.Count; 2350 var defaultStateBuffer = m_StateBuffers.defaultStateBuffer; 2351 for (var n = 0; n < controlCount; ++n) 2352 { 2353 var control = controls[n]; 2354 if (!control.hasDefaultState) 2355 continue; 2356 2357 control.m_StateBlock.Write(defaultStateBuffer, control.m_DefaultState); 2358 } 2359 2360 // Copy default state to all front and back buffers. 2361 var stateBlock = device.m_StateBlock; 2362 var deviceIndex = device.m_DeviceIndex; 2363 if (m_StateBuffers.m_PlayerStateBuffers.valid) 2364 { 2365 stateBlock.CopyToFrom(m_StateBuffers.m_PlayerStateBuffers.GetFrontBuffer(deviceIndex), defaultStateBuffer); 2366 stateBlock.CopyToFrom(m_StateBuffers.m_PlayerStateBuffers.GetBackBuffer(deviceIndex), defaultStateBuffer); 2367 } 2368 2369 #if UNITY_EDITOR 2370 if (m_StateBuffers.m_EditorStateBuffers.valid) 2371 { 2372 stateBlock.CopyToFrom(m_StateBuffers.m_EditorStateBuffers.GetFrontBuffer(deviceIndex), defaultStateBuffer); 2373 stateBlock.CopyToFrom(m_StateBuffers.m_EditorStateBuffers.GetBackBuffer(deviceIndex), defaultStateBuffer); 2374 } 2375 #endif 2376 } 2377 2378 private unsafe void InitializeDeviceState(InputDevice device) 2379 { 2380 Debug.Assert(device != null, "Device must not be null"); 2381 Debug.Assert(device.added, "Device must have been added"); 2382 Debug.Assert(device.stateBlock.byteOffset != InputStateBlock.InvalidOffset, "Device state block offset is invalid"); 2383 Debug.Assert(device.stateBlock.byteOffset + device.stateBlock.alignedSizeInBytes <= m_StateBuffers.sizePerBuffer, 2384 "Device state block is not contained in state buffer"); 2385 2386 var controls = device.allControls; 2387 var controlCount = controls.Count; 2388 var resetMaskBuffer = m_StateBuffers.resetMaskBuffer; 2389 2390 var haveControlsWithDefaultState = device.hasControlsWithDefaultState; 2391 2392 // Assume that everything in the device is noise. This way we also catch memory regions 2393 // that are not actually covered by a control and implicitly mark them as noise (e.g. the 2394 // report ID in HID input reports). 2395 // 2396 // NOTE: Noise is indicated by *unset* bits so we don't have to do anything here to start 2397 // with all-noise as we expect noise mask memory to be cleared on allocation. 2398 var noiseMaskBuffer = m_StateBuffers.noiseMaskBuffer; 2399 2400 // We first toggle all bits *on* and then toggle bits for noisy and dontReset controls *off* individually. 2401 // We do this instead of just leaving all bits *off* and then going through controls that aren't noisy/dontReset *on*. 2402 // If we did the latter, we'd have the problem that a parent control such as TouchControl would toggle on bits for 2403 // the entirety of its state block and thus cover the state of all its child controls. 2404 MemoryHelpers.SetBitsInBuffer(noiseMaskBuffer, (int)device.stateBlock.byteOffset, 0, (int)device.stateBlock.sizeInBits, false); 2405 MemoryHelpers.SetBitsInBuffer(resetMaskBuffer, (int)device.stateBlock.byteOffset, 0, (int)device.stateBlock.sizeInBits, true); 2406 2407 // Go through controls. 2408 var defaultStateBuffer = m_StateBuffers.defaultStateBuffer; 2409 for (var n = 0; n < controlCount; ++n) 2410 { 2411 var control = controls[n]; 2412 2413 // Don't allow controls that hijack state from other controls to set independent noise or dontReset flags. 2414 if (control.usesStateFromOtherControl) 2415 continue; 2416 2417 if (!control.noisy || control.dontReset) 2418 { 2419 ref var stateBlock = ref control.m_StateBlock; 2420 2421 Debug.Assert(stateBlock.byteOffset != InputStateBlock.InvalidOffset, "Byte offset is invalid on control's state block"); 2422 Debug.Assert(stateBlock.bitOffset != InputStateBlock.InvalidOffset, "Bit offset is invalid on control's state block"); 2423 Debug.Assert(stateBlock.sizeInBits != InputStateBlock.InvalidOffset, "Size is invalid on control's state block"); 2424 Debug.Assert(stateBlock.byteOffset >= device.stateBlock.byteOffset, "Control's offset is located below device's offset"); 2425 Debug.Assert(stateBlock.byteOffset + stateBlock.alignedSizeInBytes <= 2426 device.stateBlock.byteOffset + device.stateBlock.alignedSizeInBytes, "Control state block lies outside of state buffer"); 2427 2428 // If control isn't noisy, toggle its bits *on* in the noise mask. 2429 if (!control.noisy) 2430 MemoryHelpers.SetBitsInBuffer(noiseMaskBuffer, (int)stateBlock.byteOffset, (int)stateBlock.bitOffset, 2431 (int)stateBlock.sizeInBits, true); 2432 2433 // If control shouldn't be reset, toggle its bits *off* in the reset mask. 2434 if (control.dontReset) 2435 MemoryHelpers.SetBitsInBuffer(resetMaskBuffer, (int)stateBlock.byteOffset, (int)stateBlock.bitOffset, 2436 (int)stateBlock.sizeInBits, false); 2437 } 2438 2439 // If control has default state, write it into to the device's default state. 2440 if (haveControlsWithDefaultState && control.hasDefaultState) 2441 control.m_StateBlock.Write(defaultStateBuffer, control.m_DefaultState); 2442 } 2443 2444 // Copy default state to all front and back buffers. 2445 if (haveControlsWithDefaultState) 2446 { 2447 ref var deviceStateBlock = ref device.m_StateBlock; 2448 var deviceIndex = device.m_DeviceIndex; 2449 if (m_StateBuffers.m_PlayerStateBuffers.valid) 2450 { 2451 deviceStateBlock.CopyToFrom(m_StateBuffers.m_PlayerStateBuffers.GetFrontBuffer(deviceIndex), defaultStateBuffer); 2452 deviceStateBlock.CopyToFrom(m_StateBuffers.m_PlayerStateBuffers.GetBackBuffer(deviceIndex), defaultStateBuffer); 2453 } 2454 2455 #if UNITY_EDITOR 2456 if (m_StateBuffers.m_EditorStateBuffers.valid) 2457 { 2458 deviceStateBlock.CopyToFrom(m_StateBuffers.m_EditorStateBuffers.GetFrontBuffer(deviceIndex), defaultStateBuffer); 2459 deviceStateBlock.CopyToFrom(m_StateBuffers.m_EditorStateBuffers.GetBackBuffer(deviceIndex), defaultStateBuffer); 2460 } 2461 #endif 2462 } 2463 } 2464 2465 private void OnNativeDeviceDiscovered(int deviceId, string deviceDescriptor) 2466 { 2467 // Make sure we're not adding to m_AvailableDevices before we restored what we 2468 // had before a domain reload. 2469 RestoreDevicesAfterDomainReloadIfNecessary(); 2470 2471 // See if we have a disconnected device we can revive. 2472 // NOTE: We do this all the way up here as the first thing before we even parse the JSON descriptor so 2473 // if we do have a device we can revive, we can do so without allocating any GC memory. 2474 var device = TryMatchDisconnectedDevice(deviceDescriptor); 2475 2476 // Parse description, if need be. 2477 var description = device?.description ?? InputDeviceDescription.FromJson(deviceDescriptor); 2478 2479 // Add it. 2480 var markAsRemoved = false; 2481 try 2482 { 2483 // If we have a restricted set of supported devices, first check if it's a device 2484 // we should support. 2485 if (m_Settings.supportedDevices.Count > 0) 2486 { 2487 var layout = device != null ? device.m_Layout : TryFindMatchingControlLayout(ref description, deviceId); 2488 if (!IsDeviceLayoutMarkedAsSupportedInSettings(layout)) 2489 { 2490 // Not supported. Ignore device. Still will get added to m_AvailableDevices 2491 // list in finally clause below. If later the set of supported devices changes 2492 // so that the device is now supported, ApplySettings() will pull it back out 2493 // and create the device. 2494 markAsRemoved = true; 2495 return; 2496 } 2497 } 2498 2499 if (device != null) 2500 { 2501 // It's a device we pulled from the disconnected list. Update the device with the 2502 // new ID, re-add it and notify that we've reconnected. 2503 2504 device.m_DeviceId = deviceId; 2505 device.m_DeviceFlags |= InputDevice.DeviceFlags.Native; 2506 device.m_DeviceFlags &= ~InputDevice.DeviceFlags.DisabledInFrontend; 2507 device.m_DeviceFlags &= ~InputDevice.DeviceFlags.DisabledWhileInBackground; 2508 device.m_DeviceFlags &= ~InputDevice.DeviceFlags.DisabledStateHasBeenQueriedFromRuntime; 2509 2510 AddDevice(device); 2511 2512 DelegateHelpers.InvokeCallbacksSafe(ref m_DeviceChangeListeners, device, InputDeviceChange.Reconnected, 2513 k_InputOnDeviceChangeMarker, "InputSystem.onDeviceChange"); 2514 } 2515 else 2516 { 2517 // Go through normal machinery to try to create a new device. 2518 AddDevice(description, throwIfNoLayoutFound: false, deviceId: deviceId, 2519 deviceFlags: InputDevice.DeviceFlags.Native); 2520 } 2521 } 2522 // We're catching exceptions very aggressively here. The reason is that we don't want 2523 // exceptions thrown as a result of trying to create devices from device discoveries reported 2524 // by native to break the system as a whole. Instead, we want to make the error visible but then 2525 // go and work with whatever devices we *did* manage to create successfully. 2526 catch (Exception exception) 2527 { 2528 Debug.LogError($"Could not create a device for '{description}' (exception: {exception})"); 2529 } 2530 finally 2531 { 2532 // Remember it. Do this *after* the AddDevice() call above so that if there's 2533 // a listener creating layouts on the fly we won't end up matching this device and 2534 // create an InputDevice right away (which would then conflict with the one we 2535 // create in AddDevice). 2536 ArrayHelpers.AppendWithCapacity(ref m_AvailableDevices, ref m_AvailableDeviceCount, 2537 new AvailableDevice 2538 { 2539 description = description, 2540 deviceId = deviceId, 2541 isNative = true, 2542 isRemoved = markAsRemoved, 2543 }); 2544 } 2545 } 2546 2547 private JsonParser.JsonString MakeEscapedJsonString(string theString) 2548 { 2549 // 2550 // When we create the device description from the (passed from native) deviceDescriptor string in OnNativeDeviceDiscovered() 2551 // we remove any escape characters from the capabilties field when we do InputDeviceDescription.FromJson() - this decoded 2552 // description is used to create the device. 2553 // 2554 // This means that the native and managed code can have slightly different representations of the capabilities field. 2555 // 2556 // Managed: description.capabilities string, unescaped 2557 // eg "{"deviceName":"Oculus Quest", ..." 2558 // 2559 // Native: deviceDescriptor string, containing a Json encoded "capabilities" name/value pair represented by an escaped Json string 2560 // eg "{\"deviceName\":\"Oculus Quest\", ..." 2561 // 2562 // To avoid a very costly escape-skipping character-by-character string comparison in JsonParser.Json.Equals() we 2563 // reconstruct an escaped string and make an escaped JsonParser.JsonString and use that for the comparison instead. 2564 // 2565 if (string.IsNullOrEmpty(theString)) 2566 { 2567 return new JsonParser.JsonString 2568 { 2569 text = string.Empty, // text should be an empty string and not null for consistency on property comparisons 2570 hasEscapes = false 2571 }; 2572 } 2573 2574 var builder = new StringBuilder(); 2575 var length = theString.Length; 2576 var hasEscapes = false; 2577 for (var j = 0; j < length; ++j) 2578 { 2579 var ch = theString[j]; 2580 if (ch == '\\' || ch == '\"') 2581 { 2582 builder.Append('\\'); 2583 hasEscapes = true; 2584 } 2585 builder.Append(ch); 2586 } 2587 var jsonStringWithEscapes = new JsonParser.JsonString 2588 { 2589 text = builder.ToString(), 2590 hasEscapes = hasEscapes 2591 }; 2592 return jsonStringWithEscapes; 2593 } 2594 2595 private InputDevice TryMatchDisconnectedDevice(string deviceDescriptor) 2596 { 2597 for (var i = 0; i < m_DisconnectedDevicesCount; ++i) 2598 { 2599 var device = m_DisconnectedDevices[i]; 2600 var description = device.description; 2601 2602 // We don't parse the full description but rather go property by property in order to not 2603 // allocate GC memory if we can avoid it. 2604 2605 if (!InputDeviceDescription.ComparePropertyToDeviceDescriptor("interface", description.interfaceName, deviceDescriptor)) 2606 continue; 2607 if (!InputDeviceDescription.ComparePropertyToDeviceDescriptor("product", description.product, deviceDescriptor)) 2608 continue; 2609 if (!InputDeviceDescription.ComparePropertyToDeviceDescriptor("manufacturer", description.manufacturer, deviceDescriptor)) 2610 continue; 2611 if (!InputDeviceDescription.ComparePropertyToDeviceDescriptor("type", description.deviceClass, deviceDescriptor)) 2612 continue; 2613 if (!InputDeviceDescription.ComparePropertyToDeviceDescriptor("capabilities", MakeEscapedJsonString(description.capabilities), deviceDescriptor)) 2614 continue; 2615 if (!InputDeviceDescription.ComparePropertyToDeviceDescriptor("serial", description.serial, deviceDescriptor)) 2616 continue; 2617 2618 ArrayHelpers.EraseAtWithCapacity(m_DisconnectedDevices, ref m_DisconnectedDevicesCount, i); 2619 return device; 2620 } 2621 2622 return null; 2623 } 2624 2625 private void InstallBeforeUpdateHookIfNecessary() 2626 { 2627 if (m_NativeBeforeUpdateHooked || m_Runtime == null) 2628 return; 2629 2630 m_Runtime.onBeforeUpdate = OnBeforeUpdate; 2631 m_NativeBeforeUpdateHooked = true; 2632 } 2633 2634 private void RestoreDevicesAfterDomainReloadIfNecessary() 2635 { 2636 #if UNITY_EDITOR 2637 if (m_SavedDeviceStates != null) 2638 RestoreDevicesAfterDomainReload(); 2639 #endif 2640 } 2641 2642#if UNITY_EDITOR 2643 private void SyncAllDevicesWhenEditorIsActivated() 2644 { 2645 var isActive = m_Runtime.isEditorActive; 2646 if (isActive == m_EditorIsActive) 2647 return; 2648 2649 m_EditorIsActive = isActive; 2650 if (m_EditorIsActive) 2651 SyncAllDevices(); 2652 } 2653 2654 private void SyncAllDevices() 2655 { 2656 for (var i = 0; i < m_DevicesCount; ++i) 2657 { 2658 // When the editor comes back into focus, we actually do want resets to happen 2659 // for devices that don't support syncs as they will likely have missed input while 2660 // we were in the background. 2661 if (!m_Devices[i].RequestSync()) 2662 ResetDevice(m_Devices[i], issueResetCommand: true); 2663 } 2664 } 2665 2666 internal void SyncAllDevicesAfterEnteringPlayMode() 2667 { 2668 // Because we ignore all events between exiting edit mode and entering play mode, 2669 // that includes any potential device resets/syncs/etc, 2670 // we need to resync all devices after we're in play mode proper. 2671 ////TODO: this is a hacky workaround, implement a proper solution where events from sync/resets are not ignored. 2672 SyncAllDevices(); 2673 } 2674 2675#endif 2676 2677 private void WarnAboutDevicesFailingToRecreateAfterDomainReload() 2678 { 2679 // If we still have any saved device states, we have devices that we couldn't figure 2680 // out how to recreate after a domain reload. Log a warning for each of them and 2681 // let go of them. 2682 #if UNITY_EDITOR 2683 if (m_SavedDeviceStates == null) 2684 return; 2685 2686 for (var i = 0; i < m_SavedDeviceStates.Length; ++i) 2687 { 2688 ref var state = ref m_SavedDeviceStates[i]; 2689 Debug.LogWarning($"Could not recreate device '{state.name}' with layout '{state.layout}' after domain reload"); 2690 } 2691 2692 // At this point, we throw the device states away and forget about 2693 // what we had before the domain reload. 2694 m_SavedDeviceStates = null; 2695 #endif 2696 } 2697 2698 private void OnBeforeUpdate(InputUpdateType updateType) 2699 { 2700 // Restore devices before checking update mask. See InputSystem.RunInitialUpdate(). 2701 RestoreDevicesAfterDomainReloadIfNecessary(); 2702 2703 if ((updateType & m_UpdateMask) == 0) 2704 return; 2705 2706 InputStateBuffers.SwitchTo(m_StateBuffers, updateType); 2707 2708 InputUpdate.OnBeforeUpdate(updateType); 2709 2710 // For devices that have state callbacks, tell them we're carrying state over 2711 // into the next frame. 2712 if (m_HaveDevicesWithStateCallbackReceivers && updateType != InputUpdateType.BeforeRender) ////REVIEW: before-render handling is probably wrong 2713 { 2714 for (var i = 0; i < m_DevicesCount; ++i) 2715 { 2716 var device = m_Devices[i]; 2717 if (!device.hasStateCallbacks) 2718 continue; 2719 2720 // NOTE: We do *not* perform a buffer flip here as we do not want to change what is the 2721 // current and what is the previous state when we carry state forward. Rather, 2722 // OnBeforeUpdate, if it modifies state, it modifies the current state directly. 2723 // Also, for the same reasons, we do not modify the dynamic/fixed update counts 2724 // on the device. If an event comes in in the upcoming update, it should lead to 2725 // a buffer flip. 2726 2727 ((IInputStateCallbackReceiver)device).OnNextUpdate(); 2728 } 2729 } 2730 2731 DelegateHelpers.InvokeCallbacksSafe(ref m_BeforeUpdateListeners, k_InputOnBeforeUpdateMarker, "InputSystem.onBeforeUpdate"); 2732 } 2733 2734 /// <summary> 2735 /// Apply the settings in <see cref="m_Settings"/>. 2736 /// </summary> 2737 internal void ApplySettings() 2738 { 2739 // Sync update mask. 2740 var newUpdateMask = InputUpdateType.Editor; 2741 if ((m_UpdateMask & InputUpdateType.BeforeRender) != 0) 2742 { 2743 // BeforeRender updates are enabled in response to devices needing BeforeRender updates 2744 // so we always preserve this if set. 2745 newUpdateMask |= InputUpdateType.BeforeRender; 2746 } 2747 if (m_Settings.updateMode == InputSettings.s_OldUnsupportedFixedAndDynamicUpdateSetting) 2748 m_Settings.updateMode = InputSettings.UpdateMode.ProcessEventsInDynamicUpdate; 2749 switch (m_Settings.updateMode) 2750 { 2751 case InputSettings.UpdateMode.ProcessEventsInDynamicUpdate: 2752 newUpdateMask |= InputUpdateType.Dynamic; 2753 break; 2754 case InputSettings.UpdateMode.ProcessEventsInFixedUpdate: 2755 newUpdateMask |= InputUpdateType.Fixed; 2756 break; 2757 case InputSettings.UpdateMode.ProcessEventsManually: 2758 newUpdateMask |= InputUpdateType.Manual; 2759 break; 2760 default: 2761 throw new NotSupportedException("Invalid input update mode: " + m_Settings.updateMode); 2762 } 2763 2764 #if UNITY_EDITOR 2765 // In the editor, we force editor updates to be on even if InputEditorUserSettings.lockInputToGameView is 2766 // on as otherwise we'll end up accumulating events in edit mode without anyone flushing the 2767 // queue out regularly. 2768 newUpdateMask |= InputUpdateType.Editor; 2769 #endif 2770 updateMask = newUpdateMask; 2771 2772 scrollDeltaBehavior = m_Settings.scrollDeltaBehavior; 2773 2774 ////TODO: optimize this so that we don't repeatedly recreate state if we add/remove multiple devices 2775 //// (same goes for not resolving actions repeatedly) 2776 2777 // Check if there's any native device we aren't using ATM which now fits 2778 // the set of supported devices. 2779 AddAvailableDevicesThatAreNowRecognized(); 2780 2781 // If the settings restrict the set of supported devices, demote any native 2782 // device we currently have that doesn't fit the requirements. 2783 if (settings.supportedDevices.Count > 0) 2784 { 2785 for (var i = 0; i < m_DevicesCount; ++i) 2786 { 2787 var device = m_Devices[i]; 2788 var layout = device.m_Layout; 2789 2790 // If it's not in m_AvailableDevices, we don't automatically remove it. 2791 // Whatever has been added directly through AddDevice(), we keep and don't 2792 // restrict by `supportDevices`. 2793 var isInAvailableDevices = false; 2794 for (var n = 0; n < m_AvailableDeviceCount; ++n) 2795 { 2796 if (m_AvailableDevices[n].deviceId == device.deviceId) 2797 { 2798 isInAvailableDevices = true; 2799 break; 2800 } 2801 } 2802 if (!isInAvailableDevices) 2803 continue; 2804 2805 // If the device layout isn't supported according to the current settings, 2806 // remove the device. 2807 if (!IsDeviceLayoutMarkedAsSupportedInSettings(layout)) 2808 { 2809 RemoveDevice(device, keepOnListOfAvailableDevices: true); 2810 --i; 2811 } 2812 } 2813 } 2814 2815 // Apply feature flags. 2816 if (m_Settings.m_FeatureFlags != null) 2817 { 2818 #if UNITY_EDITOR 2819 runPlayerUpdatesInEditMode = m_Settings.IsFeatureEnabled(InputFeatureNames.kRunPlayerUpdatesInEditMode); 2820 #endif 2821 2822 // Extract feature flags into fields since used in hot-path 2823 m_ReadValueCachingFeatureEnabled = m_Settings.IsFeatureEnabled((InputFeatureNames.kUseReadValueCaching)); 2824 m_OptimizedControlsFeatureEnabled = m_Settings.IsFeatureEnabled((InputFeatureNames.kUseOptimizedControls)); 2825 m_ParanoidReadValueCachingChecksEnabled = m_Settings.IsFeatureEnabled((InputFeatureNames.kParanoidReadValueCachingChecks)); 2826 } 2827 2828 // Cache some values. 2829 Touchscreen.s_TapTime = settings.defaultTapTime; 2830 Touchscreen.s_TapDelayTime = settings.multiTapDelayTime; 2831 Touchscreen.s_TapRadiusSquared = settings.tapRadius * settings.tapRadius; 2832 // Extra clamp here as we can't tell what we're getting from serialized data. 2833 ButtonControl.s_GlobalDefaultButtonPressPoint = Mathf.Clamp(settings.defaultButtonPressPoint, ButtonControl.kMinButtonPressPoint, float.MaxValue); 2834 ButtonControl.s_GlobalDefaultButtonReleaseThreshold = settings.buttonReleaseThreshold; 2835 2836 // Update devices control optimization 2837 foreach (var device in devices) 2838 device.SetOptimizedControlDataTypeRecursively(); 2839 2840 // Invalidate control caches due to potential changes to processors or value readers 2841 foreach (var device in devices) 2842 device.MarkAsStaleRecursively(); 2843 2844 // Let listeners know. 2845 DelegateHelpers.InvokeCallbacksSafe(ref m_SettingsChangedListeners, 2846 k_InputOnSettingsChangeMarker, "InputSystem.onSettingsChange"); 2847 } 2848 2849 #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 2850 internal void ApplyActions() 2851 { 2852 // Let listeners know. 2853 DelegateHelpers.InvokeCallbacksSafe(ref m_ActionsChangedListeners, k_InputOnActionsChangeMarker, "InputSystem.onActionsChange"); 2854 } 2855 2856 #endif 2857 2858 internal unsafe long ExecuteGlobalCommand<TCommand>(ref TCommand command) 2859 where TCommand : struct, IInputDeviceCommandInfo 2860 { 2861 var ptr = (InputDeviceCommand*)UnsafeUtility.AddressOf(ref command); 2862 // device id is irrelevant as we route it based on fourcc internally 2863 return InputRuntime.s_Instance.DeviceCommand(0, ptr); 2864 } 2865 2866 internal void AddAvailableDevicesThatAreNowRecognized() 2867 { 2868 for (var i = 0; i < m_AvailableDeviceCount; ++i) 2869 { 2870 var id = m_AvailableDevices[i].deviceId; 2871 if (TryGetDeviceById(id) != null) 2872 continue; 2873 2874 var layout = TryFindMatchingControlLayout(ref m_AvailableDevices[i].description, id); 2875 if (!IsDeviceLayoutMarkedAsSupportedInSettings(layout)) continue; 2876 2877 if (layout.IsEmpty()) 2878 { 2879 // If it's a device coming from the runtime, disable it. 2880 if (id != InputDevice.InvalidDeviceId) 2881 { 2882 var command = DisableDeviceCommand.Create(); 2883 m_Runtime.DeviceCommand(id, ref command); 2884 } 2885 2886 continue; 2887 } 2888 2889 try 2890 { 2891 AddDevice(m_AvailableDevices[i].description, layout, deviceId: id, 2892 deviceFlags: m_AvailableDevices[i].isNative ? InputDevice.DeviceFlags.Native : 0); 2893 } 2894 catch (Exception) 2895 { 2896 // the user might have changed the layout of one device, but others in the system might still have 2897 // layouts we can't make sense of. Just quietly swallow exceptions from those so as not to spam 2898 // the user with information about devices unrelated to what was actually changed. 2899 } 2900 } 2901 } 2902 2903 private bool ShouldRunDeviceInBackground(InputDevice device) 2904 { 2905 return m_Settings.backgroundBehavior != InputSettings.BackgroundBehavior.ResetAndDisableAllDevices && 2906 device.canRunInBackground; 2907 } 2908 2909 internal void OnFocusChanged(bool focus) 2910 { 2911 #if UNITY_EDITOR 2912 SyncAllDevicesWhenEditorIsActivated(); 2913 2914 if (!m_Runtime.isInPlayMode) 2915 { 2916 m_HasFocus = focus; 2917 return; 2918 } 2919 2920 var gameViewFocus = m_Settings.editorInputBehaviorInPlayMode; 2921 #endif 2922 2923 var runInBackground = 2924 #if UNITY_EDITOR 2925 // In the editor, the player loop will always be run even if the Game View does not have focus. This 2926 // amounts to runInBackground being always true in the editor, regardless of what the setting in 2927 // the Player Settings window is. 2928 // 2929 // If, however, "Game View Focus" is set to "Exactly As In Player", we force code here down the same 2930 // path as in the player. 2931 gameViewFocus != InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView || m_Runtime.runInBackground; 2932 #else 2933 m_Runtime.runInBackground; 2934 #endif 2935 2936 var backgroundBehavior = m_Settings.backgroundBehavior; 2937 if (backgroundBehavior == InputSettings.BackgroundBehavior.IgnoreFocus && runInBackground) 2938 { 2939 // If runInBackground is true, no device changes should happen, even when focus is gained. So early out. 2940 // If runInBackground is false, we still want to sync devices when focus is gained. So we need to continue further. 2941 m_HasFocus = focus; 2942 return; 2943 } 2944 2945 #if UNITY_EDITOR 2946 // Set the current update type while we process the focus changes to make sure we 2947 // feed into the right buffer. No need to do this in the player as it doesn't have 2948 // the editor/player confusion. 2949 m_CurrentUpdate = m_UpdateMask.GetUpdateTypeForPlayer(); 2950 #endif 2951 2952 if (!focus) 2953 { 2954 // We only react to loss of focus when we will keep running in the background. If not, 2955 // we'll do nothing and just wait for focus to come back (where we then try to sync all devices). 2956 if (runInBackground) 2957 { 2958 for (var i = 0; i < m_DevicesCount; ++i) 2959 { 2960 // Determine whether to run this device in the background. 2961 var device = m_Devices[i]; 2962 if (!device.enabled || ShouldRunDeviceInBackground(device)) 2963 continue; 2964 2965 // Disable the device. This will also soft-reset it. 2966 EnableOrDisableDevice(device, false, DeviceDisableScope.TemporaryWhilePlayerIsInBackground); 2967 2968 // In case we invoked a callback that messed with our device array, adjust our index. 2969 var index = m_Devices.IndexOfReference(device, m_DevicesCount); 2970 if (index == -1) 2971 --i; 2972 else 2973 i = index; 2974 } 2975 } 2976 } 2977 else 2978 { 2979 // On focus gain, reenable and sync devices. 2980 for (var i = 0; i < m_DevicesCount; ++i) 2981 { 2982 var device = m_Devices[i]; 2983 2984 // Re-enable the device if we disabled it on focus loss. This will also issue a sync. 2985 if (device.disabledWhileInBackground) 2986 EnableOrDisableDevice(device, true, DeviceDisableScope.TemporaryWhilePlayerIsInBackground); 2987 // Try to sync. If it fails and we didn't run in the background, perform 2988 // a reset instead. This is to cope with backends that are unable to sync but 2989 // may still retain state which now may be outdated because the input device may 2990 // have changed state while we weren't running. So at least make the backend flush 2991 // its state (if any). 2992 else if (device.enabled && !runInBackground && !device.RequestSync()) 2993 ResetDevice(device); 2994 } 2995 } 2996 2997 #if UNITY_EDITOR 2998 m_CurrentUpdate = InputUpdateType.None; 2999 #endif 3000 3001 // We set this *after* the block above as defaultUpdateType is influenced by the setting. 3002 m_HasFocus = focus; 3003 } 3004 3005#if UNITY_EDITOR 3006 internal void LeavePlayMode() 3007 { 3008 // Reenable all devices and reset their play mode state. 3009 m_CurrentUpdate = InputUpdate.GetUpdateTypeForPlayer(m_UpdateMask); 3010 InputStateBuffers.SwitchTo(m_StateBuffers, m_CurrentUpdate); 3011 for (var i = 0; i < m_DevicesCount; ++i) 3012 { 3013 var device = m_Devices[i]; 3014 if (device.disabledWhileInBackground) 3015 EnableOrDisableDevice(device, true, scope: DeviceDisableScope.TemporaryWhilePlayerIsInBackground); 3016 ResetDevice(device, alsoResetDontResetControls: true); 3017 } 3018 m_CurrentUpdate = default; 3019 } 3020 3021 private void OnPlayerLoopInitialization() 3022 { 3023 if (!gameIsPlaying || // if game is not playing 3024 !InputUpdate.s_LatestUpdateType.IsEditorUpdate() || // or last update was not editor update 3025 !InputUpdate.s_LatestNonEditorUpdateType.IsPlayerUpdate()) // or update before that was not player update 3026 return; // then no need to restore anything 3027 3028 InputUpdate.RestoreStateAfterEditorUpdate(); 3029 InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup = latestNonEditorTimeOffsetToRealtimeSinceStartup; 3030 InputStateBuffers.SwitchTo(m_StateBuffers, InputUpdate.s_LatestUpdateType); 3031 } 3032 3033#endif 3034 3035 internal bool ShouldRunUpdate(InputUpdateType updateType) 3036 { 3037 // We perform a "null" update after domain reloads and on startup to get our devices 3038 // in place before the runtime calls MonoBehaviour callbacks. See InputSystem.RunInitialUpdate(). 3039 if (updateType == InputUpdateType.None) 3040 return true; 3041 3042 var mask = m_UpdateMask; 3043 3044#if UNITY_EDITOR 3045 // If the player isn't running, the only thing we run is editor updates, except if 3046 // explicitly overriden via `runUpdatesInEditMode`. 3047 // NOTE: This means that in edit mode (outside of play mode) we *never* switch to player 3048 // input state. So, any script anywhere will see input state from the editor. If you 3049 // have an [ExecuteInEditMode] MonoBehaviour and it polls the gamepad, for example, 3050 // it will see gamepad inputs going to the editor and respond to them. 3051 if (!gameIsPlaying && updateType != InputUpdateType.Editor && !runPlayerUpdatesInEditMode) 3052 return false; 3053#endif 3054 3055 return (updateType & mask) != 0; 3056 } 3057 3058 /// <summary> 3059 /// Process input events. 3060 /// </summary> 3061 /// <param name="updateType"></param> 3062 /// <param name="eventBuffer"></param> 3063 /// <remarks> 3064 /// This method is the core workhorse of the input system. It is called from <see cref="UnityEngineInternal.Input.NativeInputSystem"/>. 3065 /// Usually this happens in response to the player loop running and triggering updates at set points. However, 3066 /// updates can also be manually triggered through <see cref="InputSystem.Update"/>. 3067 /// 3068 /// The method receives the event buffer used internally by the runtime to collect events. 3069 /// 3070 /// Note that update types do *NOT* say what the events we receive are for. The update type only indicates 3071 /// where in the Unity's application loop we got called from. Where the event data goes depends wholly on 3072 /// which buffers we activate in the update and write the event data into. 3073 /// </remarks> 3074 /// <exception cref="InvalidOperationException">Thrown if OnUpdate is called recursively.</exception> 3075 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1809:AvoidExcessiveLocals", Justification = "TODO: Refactor later.")] 3076 private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer eventBuffer) 3077 { 3078 // NOTE: This is *not* using try/finally as we've seen unreliability in the EndSample() 3079 // execution (and we're not sure where it's coming from). 3080 k_InputUpdateProfilerMarker.Begin(); 3081 3082 if (m_InputEventStream.isOpen) 3083 { 3084 k_InputUpdateProfilerMarker.End(); 3085 throw new InvalidOperationException("Already have an event buffer set! Was OnUpdate() called recursively?"); 3086 } 3087 3088 // Restore devices before checking update mask. See InputSystem.RunInitialUpdate(). 3089 RestoreDevicesAfterDomainReloadIfNecessary(); 3090 3091 // In the editor, we issue a sync on all devices when the editor comes back to the foreground. 3092 #if UNITY_EDITOR 3093 SyncAllDevicesWhenEditorIsActivated(); 3094 #endif 3095 3096 if ((updateType & m_UpdateMask) == 0) 3097 { 3098 k_InputUpdateProfilerMarker.End(); 3099 return; 3100 } 3101 3102 WarnAboutDevicesFailingToRecreateAfterDomainReload(); 3103 3104 // First update sends out startup analytics. 3105 #if UNITY_ANALYTICS || UNITY_EDITOR 3106 if (!m_HaveSentStartupAnalytics) 3107 { 3108 InputAnalytics.OnStartup(this); 3109 m_HaveSentStartupAnalytics = true; 3110 } 3111 #endif 3112 3113 // Update metrics. 3114 ++m_Metrics.totalUpdateCount; 3115 3116 #if UNITY_EDITOR 3117 // If current update is editor update and previous update was non-editor, 3118 // store the time offset so we can restore it right after editor update is complete 3119 if (((updateType & InputUpdateType.Editor) == InputUpdateType.Editor) && (m_CurrentUpdate & InputUpdateType.Editor) == 0) 3120 latestNonEditorTimeOffsetToRealtimeSinceStartup = 3121 InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup; 3122 #endif 3123 3124 // Store current time offset. 3125 InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup = m_Runtime.currentTimeOffsetToRealtimeSinceStartup; 3126 3127 InputStateBuffers.SwitchTo(m_StateBuffers, updateType); 3128 3129 m_CurrentUpdate = updateType; 3130 InputUpdate.OnUpdate(updateType); 3131 3132 // Ensure optimized controls are in valid state 3133 CheckAllDevicesOptimizedControlsHaveValidState(); 3134 3135 var shouldProcessActionTimeouts = updateType.IsPlayerUpdate() && gameIsPlaying; 3136 3137 // See if we're supposed to only take events up to a certain time. 3138 // NOTE: We do not require the events in the queue to be sorted. Instead, we will walk over 3139 // all events in the buffer each time. Note that if there are multiple events for the same 3140 // device, it depends on the producer of these events to queue them in correct order. 3141 // Otherwise, once an event with a newer timestamp has been processed, events coming later 3142 // in the buffer and having older timestamps will get rejected. 3143 3144 var currentTime = updateType == InputUpdateType.Fixed ? m_Runtime.currentTimeForFixedUpdate : m_Runtime.currentTime; 3145 var timesliceEvents = (updateType == InputUpdateType.Fixed || updateType == InputUpdateType.BeforeRender) && 3146 InputSystem.settings.updateMode == InputSettings.UpdateMode.ProcessEventsInFixedUpdate; 3147 3148 // Figure out if we can just flush the buffer and early out. 3149 var canFlushBuffer = 3150 false 3151#if UNITY_EDITOR 3152 // If out of focus and runInBackground is off and ExactlyAsInPlayer is on, discard input. 3153 || (!gameHasFocus && m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView && 3154 (!m_Runtime.runInBackground || 3155 m_Settings.backgroundBehavior == InputSettings.BackgroundBehavior.ResetAndDisableAllDevices)) 3156#else 3157 || (!gameHasFocus && !m_Runtime.runInBackground) 3158#endif 3159 ; 3160 var canEarlyOut = 3161 // Early out if there's no events to process. 3162 eventBuffer.eventCount == 0 3163 || canFlushBuffer 3164 3165#if UNITY_EDITOR 3166 // If we're in the background and not supposed to process events in this update (but somehow 3167 // still ended up here), we're done. 3168 || ((!gameHasFocus || gameShouldGetInputRegardlessOfFocus) && 3169 ((m_Settings.backgroundBehavior == InputSettings.BackgroundBehavior.ResetAndDisableAllDevices && updateType != InputUpdateType.Editor) 3170 || (m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode.AllDevicesRespectGameViewFocus && updateType != InputUpdateType.Editor) 3171 || (m_Settings.backgroundBehavior == InputSettings.BackgroundBehavior.IgnoreFocus && m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView && updateType == InputUpdateType.Editor) 3172 ) 3173 // When the game is playing and has focus, we never process input in editor updates. All we 3174 // do is just switch to editor state buffers and then exit. 3175 || (gameIsPlaying && gameHasFocus && updateType == InputUpdateType.Editor)) 3176#endif 3177 ; 3178 3179 3180#if UNITY_EDITOR 3181 var dropStatusEvents = false; 3182 if (!gameIsPlaying && gameShouldGetInputRegardlessOfFocus && (eventBuffer.sizeInBytes > (100 * 1024))) 3183 { 3184 // If the game is not playing but we're sending all input events to the game, the buffer can just grow unbounded. 3185 // So, in that case, set a flag to say we'd like to drop status events, and do not early out. 3186 canEarlyOut = false; 3187 dropStatusEvents = true; 3188 } 3189#endif 3190 3191 if (canEarlyOut) 3192 { 3193 // Normally, we process action timeouts after first processing all events. If we have no 3194 // events, we still need to check timeouts. 3195 if (shouldProcessActionTimeouts) 3196 ProcessStateChangeMonitorTimeouts(); 3197 3198 k_InputUpdateProfilerMarker.End(); 3199 InvokeAfterUpdateCallback(updateType); 3200 if (canFlushBuffer) 3201 eventBuffer.Reset(); 3202 m_CurrentUpdate = default; 3203 return; 3204 } 3205 3206 var processingStartTime = Stopwatch.GetTimestamp(); 3207 var totalEventLag = 0.0; 3208 3209 #if UNITY_EDITOR 3210 var isPlaying = gameIsPlaying; 3211 #endif 3212 3213 try 3214 { 3215 m_InputEventStream = new InputEventStream(ref eventBuffer, m_Settings.maxQueuedEventsPerUpdate); 3216 var totalEventBytesProcessed = 0U; 3217 3218 InputEvent* skipEventMergingFor = null; 3219 3220 // Handle events. 3221 while (m_InputEventStream.remainingEventCount > 0) 3222 { 3223 InputDevice device = null; 3224 var currentEventReadPtr = m_InputEventStream.currentEventPtr; 3225 3226 Debug.Assert(!currentEventReadPtr->handled, "Event in buffer is already marked as handled"); 3227 3228 // In before render updates, we only take state events and only those for devices 3229 // that have before render updates enabled. 3230 if (updateType == InputUpdateType.BeforeRender) 3231 { 3232 while (m_InputEventStream.remainingEventCount > 0) 3233 { 3234 Debug.Assert(!currentEventReadPtr->handled, 3235 "Iterated to event in buffer that is already marked as handled"); 3236 3237 device = TryGetDeviceById(currentEventReadPtr->deviceId); 3238 if (device != null && device.updateBeforeRender && 3239 (currentEventReadPtr->type == StateEvent.Type || 3240 currentEventReadPtr->type == DeltaStateEvent.Type)) 3241 break; 3242 3243 currentEventReadPtr = m_InputEventStream.Advance(leaveEventInBuffer: true); 3244 } 3245 } 3246 3247 if (m_InputEventStream.remainingEventCount == 0) 3248 break; 3249 3250 var currentEventTimeInternal = currentEventReadPtr->internalTime; 3251 var currentEventType = currentEventReadPtr->type; 3252 3253#if UNITY_EDITOR 3254 if (dropStatusEvents) 3255 { 3256 // If the type here is a status event, ask advance not to leave the event in the buffer. Otherwise, leave it there. 3257 if (currentEventType == StateEvent.Type || currentEventType == DeltaStateEvent.Type || currentEventType == IMECompositionEvent.Type) 3258 m_InputEventStream.Advance(false); 3259 else 3260 m_InputEventStream.Advance(true); 3261 3262 continue; 3263 } 3264#endif 3265 3266 // In the editor, we discard all input events that occur in-between exiting edit mode and having 3267 // entered play mode as otherwise we'll spill a bunch of UI events that have occurred while the 3268 // UI was sort of neither in this mode nor in that mode. This would usually lead to the game receiving 3269 // an accumulation of spurious inputs right in one of its first updates. 3270 // 3271 // NOTE: There's a chance the solution here will prove inadequate on the long run. We may do things 3272 // here such as throwing partial touches away and then letting the rest of a touch go through. 3273 // Could be that ultimately we need to issue a full reset of all devices at the beginning of 3274 // play mode in the editor. 3275#if UNITY_EDITOR 3276 if ((currentEventType == StateEvent.Type || 3277 currentEventType == DeltaStateEvent.Type) && 3278 (updateType & InputUpdateType.Editor) == 0 && 3279 InputSystem.s_SystemObject.exitEditModeTime > 0 && 3280 currentEventTimeInternal >= InputSystem.s_SystemObject.exitEditModeTime && 3281 (currentEventTimeInternal < InputSystem.s_SystemObject.enterPlayModeTime || 3282 InputSystem.s_SystemObject.enterPlayModeTime == 0)) 3283 { 3284 m_InputEventStream.Advance(false); 3285 continue; 3286 } 3287#endif 3288 3289 // If we're timeslicing, check if the event time is within limits. 3290 if (timesliceEvents && currentEventTimeInternal >= currentTime) 3291 { 3292 m_InputEventStream.Advance(true); 3293 continue; 3294 } 3295 3296 // If we can't find the device, ignore the event. 3297 if (device == null) 3298 device = TryGetDeviceById(currentEventReadPtr->deviceId); 3299 if (device == null) 3300 { 3301#if UNITY_EDITOR 3302 ////TODO: see if this is a device we haven't created and if so, just ignore 3303 m_Diagnostics?.OnCannotFindDeviceForEvent(new InputEventPtr(currentEventReadPtr)); 3304#endif 3305 3306 m_InputEventStream.Advance(false); 3307 continue; 3308 } 3309 3310 // In the editor, we may need to bump events from editor updates into player updates 3311 // and vice versa. 3312#if UNITY_EDITOR 3313 if (isPlaying && !gameHasFocus) 3314 { 3315 if (m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode 3316 .PointersAndKeyboardsRespectGameViewFocus && 3317 m_Settings.backgroundBehavior != 3318 InputSettings.BackgroundBehavior.ResetAndDisableAllDevices) 3319 { 3320 var isPointerOrKeyboard = device is Pointer || device is Keyboard; 3321 if (updateType != InputUpdateType.Editor) 3322 { 3323 // Let everything but pointer and keyboard input through. 3324 // If the event is from a pointer or keyboard, leave it in the buffer so it can be dealt with 3325 // in a subsequent editor update. Otherwise, take it out. 3326 if (isPointerOrKeyboard) 3327 { 3328 m_InputEventStream.Advance(true); 3329 continue; 3330 } 3331 } 3332 else 3333 { 3334 // Let only pointer and keyboard input through. 3335 if (!isPointerOrKeyboard) 3336 { 3337 m_InputEventStream.Advance(true); 3338 continue; 3339 } 3340 } 3341 } 3342 } 3343#endif 3344 3345 // If device is disabled, we let the event through only in certain cases. 3346 // Removal and configuration change events should always be processed. 3347 if (!device.enabled && 3348 currentEventType != DeviceRemoveEvent.Type && 3349 currentEventType != DeviceConfigurationEvent.Type && 3350 (device.m_DeviceFlags & (InputDevice.DeviceFlags.DisabledInRuntime | 3351 InputDevice.DeviceFlags.DisabledWhileInBackground)) != 0) 3352 { 3353#if UNITY_EDITOR 3354 // If the device is disabled in the backend, getting events for them 3355 // is something that indicates a problem in the backend so diagnose. 3356 if ((device.m_DeviceFlags & InputDevice.DeviceFlags.DisabledInRuntime) != 0) 3357 m_Diagnostics?.OnEventForDisabledDevice(currentEventReadPtr, device); 3358#endif 3359 3360 m_InputEventStream.Advance(false); 3361 continue; 3362 } 3363 3364 // Check if the device wants to merge successive events. 3365 if (!settings.disableRedundantEventsMerging && device.hasEventMerger && currentEventReadPtr != skipEventMergingFor) 3366 { 3367 // NOTE: This relies on events in the buffer being consecutive for the same device. This is not 3368 // necessarily the case for events coming in from the background event queue where parallel 3369 // producers may create interleaved input sequences. This will be fixed once we have the 3370 // new buffering scheme for input events working in the native runtime. 3371 3372 var nextEvent = m_InputEventStream.Peek(); 3373 // If there is next event after current one. 3374 if ((nextEvent != null) 3375 // And if next event is for the same device. 3376 && (currentEventReadPtr->deviceId == nextEvent->deviceId) 3377 // And if next event is in the same timeslicing slot. 3378 && (timesliceEvents ? (nextEvent->internalTime < currentTime) : true) 3379 ) 3380 { 3381 // Then try to merge current event into next event. 3382 if (((IEventMerger)device).MergeForward(currentEventReadPtr, nextEvent)) 3383 { 3384 // And if succeeded, skip current event, as it was merged into next event. 3385 m_InputEventStream.Advance(false); 3386 continue; 3387 } 3388 3389 // If we can't merge current event with next one for any reason, we assume the next event 3390 // carries crucial entropy (button changed state, phase changed, counter changed, etc). 3391 // Hence semantic meaning for current event is "can't merge current with next because next is different". 3392 // But semantic meaning for next event is "next event carries important information and should be preserved", 3393 // from that point of view next event should not be merged with current nor with _next after next_ event. 3394 // 3395 // For example, given such stream of events: 3396 // Mouse Mouse Mouse Mouse Mouse Mouse Mouse 3397 // Event no1 Event no2 Event no3 Event no4 Event no5 Event no6 Event no7 3398 // Time 1 Time 2 Time 3 Time 4 Time 5 Time 6 Time 7 3399 // Pos(10,20) Pos(12,21) Pos(13,23) Pos(14,24) Pos(16,25) Pos(17,27) Pos(18,28) 3400 // Delta(1,1) Delta(2,1) Delta(1,2) Delta(1,1) Delta(2,1) Delta(1,2) Delta(1,1) 3401 // BtnLeft(0) BtnLeft(0) BtnLeft(0) BtnLeft(1) BtnLeft(1) BtnLeft(1) BtnLeft(1) 3402 // 3403 // if we then merge without skipping next event here: 3404 // Mouse Mouse 3405 // Event no3 Event no7 3406 // Time 3 Time 7 3407 // Pos(13,23) Pos(18,28) 3408 // Delta(4,4) Delta(5,5) 3409 // BtnLeft(0) BtnLeft(1) 3410 // 3411 // As you can see, the event no4 containing mouse button press was lost, 3412 // and with it we lose the important information of timestamp of mouse button press. 3413 // 3414 // With skipping merging next event we will get: 3415 // Mouse Mouse Mouse 3416 // Time 3 Time 4 Time 7 3417 // Event no3 Event no4 Event no7 3418 // Pos(13,23) Pos(14,24) Pos(18,28) 3419 // Delta(3,3) Delta(1,1) Delta(4,4) 3420 // BtnLeft(0) BtnLeft(1) BtnLeft(1) 3421 // 3422 // And no4 is preserved, with the exact timestamp of button press. 3423 skipEventMergingFor = nextEvent; 3424 } 3425 } 3426 3427 // Give the device a chance to do something with data before we propagate it to event listeners. 3428 if (device.hasEventPreProcessor) 3429 { 3430#if UNITY_EDITOR 3431 var eventSizeBeforePreProcessor = currentEventReadPtr->sizeInBytes; 3432#endif 3433 var shouldProcess = ((IEventPreProcessor)device).PreProcessEvent(currentEventReadPtr); 3434#if UNITY_EDITOR 3435 if (currentEventReadPtr->sizeInBytes > eventSizeBeforePreProcessor) 3436 { 3437 k_InputUpdateProfilerMarker.End(); 3438 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."); 3439 } 3440#endif 3441 if (!shouldProcess) 3442 { 3443 // Skip event if PreProcessEvent considers it to be irrelevant. 3444 m_InputEventStream.Advance(false); 3445 continue; 3446 } 3447 } 3448 3449 // Give listeners a shot at the event. 3450 // NOTE: We call listeners also for events where the device is disabled. This is crucial for code 3451 // such as TouchSimulation that disables the originating devices and then uses its events to 3452 // create simulated events from. 3453 if (m_EventListeners.length > 0) 3454 { 3455 DelegateHelpers.InvokeCallbacksSafe(ref m_EventListeners, 3456 new InputEventPtr(currentEventReadPtr), device, k_InputOnEventMarker, "InputSystem.onEvent"); 3457 3458 // If a listener marks the event as handled, we don't process it further. 3459 if (currentEventReadPtr->handled) 3460 { 3461 m_InputEventStream.Advance(false); 3462 continue; 3463 } 3464 } 3465 3466 // Update metrics. 3467 if (currentEventTimeInternal <= currentTime) 3468 totalEventLag += currentTime - currentEventTimeInternal; 3469 ++m_Metrics.totalEventCount; 3470 m_Metrics.totalEventBytes += (int)currentEventReadPtr->sizeInBytes; 3471 3472 // Process. 3473 switch (currentEventType) 3474 { 3475 case StateEvent.Type: 3476 case DeltaStateEvent.Type: 3477 3478 var eventPtr = new InputEventPtr(currentEventReadPtr); 3479 3480 // Ignore the event if the last state update we received for the device was 3481 // newer than this state event is. We don't allow devices to go back in time. 3482 // 3483 // NOTE: We make an exception here for devices that implement IInputStateCallbackReceiver (such 3484 // as Touchscreen). For devices that dynamically incorporate state it can be hard ensuring 3485 // a global ordering of events as there may be multiple substreams (e.g. each individual touch) 3486 // that are generated in the backend and would require considerable work to ensure monotonically 3487 // increasing timestamps across all such streams. 3488 var deviceIsStateCallbackReceiver = device.hasStateCallbacks; 3489 if (currentEventTimeInternal < device.m_LastUpdateTimeInternal && 3490 !(deviceIsStateCallbackReceiver && device.stateBlock.format != eventPtr.stateFormat)) 3491 { 3492#if UNITY_EDITOR 3493 m_Diagnostics?.OnEventTimestampOutdated(new InputEventPtr(currentEventReadPtr), device); 3494#elif UNITY_ANDROID 3495 // Android keyboards can send events out of order: Holding down a key will send multiple 3496 // presses after a short time, like on most platforms. Unfortunately, on Android, the 3497 // last of these "presses" can be timestamped to be after the event of the key release. 3498 // If that happens, we'd skip the keyUp here, and the device state will have the key 3499 // "stuck" pressed. So, special case here to not skip keyboard events on Android. ISXB-475 3500 // N.B. Android seems to have similar issues with touch input (OnStateEvent, Touchscreen.cs) 3501 if (!(device is Keyboard)) 3502#endif 3503 break; 3504 } 3505 3506 // Update the state of the device from the event. If the device is an IInputStateCallbackReceiver, 3507 // let the device handle the event. If not, we do it ourselves. 3508 var haveChangedStateOtherThanNoise = true; 3509 if (deviceIsStateCallbackReceiver) 3510 { 3511 m_ShouldMakeCurrentlyUpdatingDeviceCurrent = true; 3512 // NOTE: We leave it to the device to make sure the event has the right format. This allows the 3513 // device to handle multiple different incoming formats. 3514 ((IInputStateCallbackReceiver)device).OnStateEvent(eventPtr); 3515 3516 haveChangedStateOtherThanNoise = m_ShouldMakeCurrentlyUpdatingDeviceCurrent; 3517 } 3518 else 3519 { 3520 // If the state format doesn't match, ignore the event. 3521 if (device.stateBlock.format != eventPtr.stateFormat) 3522 { 3523#if UNITY_EDITOR 3524 m_Diagnostics?.OnEventFormatMismatch(currentEventReadPtr, device); 3525#endif 3526 break; 3527 } 3528 3529 haveChangedStateOtherThanNoise = UpdateState(device, eventPtr, updateType); 3530 } 3531 3532 totalEventBytesProcessed += eventPtr.sizeInBytes; 3533 3534 device.m_CurrentProcessedEventBytesOnUpdate += eventPtr.sizeInBytes; 3535 3536 // Update timestamp on device. 3537 // NOTE: We do this here and not in UpdateState() so that InputState.Change() will *NOT* change timestamps. 3538 // Only events should. If running play mode updates in editor, we want to defer to the play mode 3539 // callbacks to set the last update time to avoid dropping events only processed by the editor state. 3540 if (device.m_LastUpdateTimeInternal <= eventPtr.internalTime 3541#if UNITY_EDITOR 3542 && !(updateType == InputUpdateType.Editor && runPlayerUpdatesInEditMode) 3543#endif 3544 ) 3545 device.m_LastUpdateTimeInternal = eventPtr.internalTime; 3546 3547 // Make device current. Again, only do this when receiving events. 3548 if (haveChangedStateOtherThanNoise) 3549 device.MakeCurrent(); 3550 3551 break; 3552 3553 case TextEvent.Type: 3554 { 3555 var textEventPtr = (TextEvent*)currentEventReadPtr; 3556 if (device is ITextInputReceiver textInputReceiver) 3557 { 3558 var utf32Char = textEventPtr->character; 3559 if (utf32Char >= 0x10000) 3560 { 3561 // Send surrogate pair. 3562 utf32Char -= 0x10000; 3563 var highSurrogate = 0xD800 + ((utf32Char >> 10) & 0x3FF); 3564 var lowSurrogate = 0xDC00 + (utf32Char & 0x3FF); 3565 3566 textInputReceiver.OnTextInput((char)highSurrogate); 3567 textInputReceiver.OnTextInput((char)lowSurrogate); 3568 } 3569 else 3570 { 3571 // Send single, plain character. 3572 textInputReceiver.OnTextInput((char)utf32Char); 3573 } 3574 } 3575 3576 break; 3577 } 3578 3579 case IMECompositionEvent.Type: 3580 { 3581 var imeEventPtr = (IMECompositionEvent*)currentEventReadPtr; 3582 var textInputReceiver = device as ITextInputReceiver; 3583 textInputReceiver?.OnIMECompositionChanged(imeEventPtr->compositionString); 3584 break; 3585 } 3586 3587 case DeviceRemoveEvent.Type: 3588 { 3589 RemoveDevice(device, keepOnListOfAvailableDevices: false); 3590 3591 // If it's a native device with a description, put it on the list of disconnected 3592 // devices. 3593 if (device.native && !device.description.empty) 3594 { 3595 ArrayHelpers.AppendWithCapacity(ref m_DisconnectedDevices, 3596 ref m_DisconnectedDevicesCount, device); 3597 DelegateHelpers.InvokeCallbacksSafe(ref m_DeviceChangeListeners, 3598 device, InputDeviceChange.Disconnected, k_InputOnDeviceChangeMarker, "InputSystem.onDeviceChange"); 3599 } 3600 3601 break; 3602 } 3603 3604 case DeviceConfigurationEvent.Type: 3605 device.NotifyConfigurationChanged(); 3606 InputActionState.OnDeviceChange(device, InputDeviceChange.ConfigurationChanged); 3607 DelegateHelpers.InvokeCallbacksSafe(ref m_DeviceChangeListeners, 3608 device, InputDeviceChange.ConfigurationChanged, k_InputOnDeviceChangeMarker, "InputSystem.onDeviceChange"); 3609 break; 3610 3611 case DeviceResetEvent.Type: 3612 ResetDevice(device, 3613 alsoResetDontResetControls: ((DeviceResetEvent*)currentEventReadPtr)->hardReset); 3614 break; 3615 } 3616 3617 m_InputEventStream.Advance(leaveEventInBuffer: false); 3618 3619 // Discard events in case the maximum event bytes per update has been exceeded 3620 if (AreMaximumEventBytesPerUpdateExceeded(totalEventBytesProcessed)) 3621 break; 3622 } 3623 3624 m_Metrics.totalEventProcessingTime += 3625 ((double)(Stopwatch.GetTimestamp() - processingStartTime)) / Stopwatch.Frequency; 3626 m_Metrics.totalEventLagTime += totalEventLag; 3627 3628 ResetCurrentProcessedEventBytesForDevices(); 3629 3630 m_InputEventStream.Close(ref eventBuffer); 3631 } 3632 catch (Exception) 3633 { 3634 // We need to restore m_InputEventStream to a sound state 3635 // to avoid failing recursive OnUpdate check next frame. 3636 k_InputUpdateProfilerMarker.End(); 3637 m_InputEventStream.CleanUpAfterException(); 3638 throw; 3639 } 3640 3641 if (shouldProcessActionTimeouts) 3642 ProcessStateChangeMonitorTimeouts(); 3643 3644 k_InputUpdateProfilerMarker.End(); 3645 ////FIXME: need to ensure that if someone calls QueueEvent() from an onAfterUpdate callback, we don't end up with a 3646 //// mess in the event buffer 3647 //// same goes for events that someone may queue from a change monitor callback 3648 InvokeAfterUpdateCallback(updateType); 3649 m_CurrentUpdate = default; 3650 } 3651 3652 bool AreMaximumEventBytesPerUpdateExceeded(uint totalEventBytesProcessed) 3653 { 3654 if (m_Settings.maxEventBytesPerUpdate > 0 && 3655 totalEventBytesProcessed >= m_Settings.maxEventBytesPerUpdate) 3656 { 3657 var eventsProcessedByDeviceLog = String.Empty; 3658 // Only log the events processed by devices in last update call if we are in debug mode. 3659 // This is to avoid the slightest overhead in release builds of having to iterate over all devices and 3660 // reset the byte count, by the end of every update call with ResetCurrentProcessedEventBytesForDevices(). 3661 if (Debug.isDebugBuild) 3662 eventsProcessedByDeviceLog = $"Total events processed by devices in last update call:\n{MakeStringWithEventsProcessedByDevice()}"; 3663 3664 Debug.LogError( 3665 "Exceeded budget for maximum input event throughput per InputSystem.Update(). Discarding remaining events. " 3666 + "Increase InputSystem.settings.maxEventBytesPerUpdate or set it to 0 to remove the limit.\n" 3667 + eventsProcessedByDeviceLog); 3668 3669 return true; 3670 } 3671 3672 return false; 3673 } 3674 3675 private string MakeStringWithEventsProcessedByDevice() 3676 { 3677 var eventsProcessedByDeviceLog = new StringBuilder(); 3678 for (int i = 0; i < m_DevicesCount; i++) 3679 { 3680 var deviceToLog = devices[i]; 3681 if (deviceToLog != null && deviceToLog.m_CurrentProcessedEventBytesOnUpdate > 0) 3682 eventsProcessedByDeviceLog.Append($" - {deviceToLog.m_CurrentProcessedEventBytesOnUpdate} bytes processed by {deviceToLog}\n"); 3683 } 3684 return eventsProcessedByDeviceLog.ToString(); 3685 } 3686 3687 // Reset the number of bytes processed by devices in the current update, for debug builds. 3688 // This is to avoid the slightest overhead in release builds of having to iterate over all devices connected. 3689 private void ResetCurrentProcessedEventBytesForDevices() 3690 { 3691 if (Debug.isDebugBuild) 3692 { 3693 for (var i = 0; i < m_DevicesCount; i++) 3694 { 3695 var device = m_Devices[i]; 3696 if (device != null && device.m_CurrentProcessedEventBytesOnUpdate > 0) 3697 { 3698 device.m_CurrentProcessedEventBytesOnUpdate = 0; 3699 } 3700 } 3701 } 3702 } 3703 3704 // Only do this check in editor in hope that it will be sufficient to catch any misuse during development. 3705 [Conditional("UNITY_EDITOR")] 3706 void CheckAllDevicesOptimizedControlsHaveValidState() 3707 { 3708 if (!InputSystem.s_Manager.m_OptimizedControlsFeatureEnabled) 3709 return; 3710 3711 foreach (var device in devices) 3712 device.EnsureOptimizationTypeHasNotChanged(); 3713 } 3714 3715 private void InvokeAfterUpdateCallback(InputUpdateType updateType) 3716 { 3717 // don't invoke the after update callback if this is an editor update and the game is playing. We 3718 // skip event processing when playing in the editor and the game has focus, which means that any 3719 // handlers for this delegate that query input state during this update will get no values. 3720 if (updateType == InputUpdateType.Editor && gameIsPlaying) 3721 return; 3722 3723 DelegateHelpers.InvokeCallbacksSafe(ref m_AfterUpdateListeners, 3724 k_InputOnAfterUpdateMarker, "InputSystem.onAfterUpdate"); 3725 } 3726 3727 private bool m_ShouldMakeCurrentlyUpdatingDeviceCurrent; 3728 3729 // This is a dirty hot fix to expose entropy from device back to input manager to make a choice if we want to make device current or not. 3730 // A proper fix would be to change IInputStateCallbackReceiver.OnStateEvent to return bool to make device current or not. 3731 internal void DontMakeCurrentlyUpdatingDeviceCurrent() 3732 { 3733 m_ShouldMakeCurrentlyUpdatingDeviceCurrent = false; 3734 } 3735 3736 internal unsafe bool UpdateState(InputDevice device, InputEvent* eventPtr, InputUpdateType updateType) 3737 { 3738 Debug.Assert(eventPtr != null, "Received NULL event ptr"); 3739 3740 var stateBlockOfDevice = device.m_StateBlock; 3741 var stateBlockSizeOfDevice = stateBlockOfDevice.sizeInBits / 8; // Always byte-aligned; avoid calling alignedSizeInBytes. 3742 var offsetInDeviceStateToCopyTo = 0u; 3743 uint sizeOfStateToCopy; 3744 uint receivedStateSize; 3745 byte* ptrToReceivedState; 3746 FourCC receivedStateFormat; 3747 3748 // Grab state data from event and decide where to copy to and how much to copy. 3749 if (eventPtr->type == StateEvent.Type) 3750 { 3751 var stateEventPtr = (StateEvent*)eventPtr; 3752 receivedStateFormat = stateEventPtr->stateFormat; 3753 receivedStateSize = stateEventPtr->stateSizeInBytes; 3754 ptrToReceivedState = (byte*)stateEventPtr->state; 3755 3756 // Ignore extra state at end of event. 3757 sizeOfStateToCopy = receivedStateSize; 3758 if (sizeOfStateToCopy > stateBlockSizeOfDevice) 3759 sizeOfStateToCopy = stateBlockSizeOfDevice; 3760 } 3761 else 3762 { 3763 Debug.Assert(eventPtr->type == DeltaStateEvent.Type, "Given event must either be a StateEvent or a DeltaStateEvent"); 3764 3765 var deltaEventPtr = (DeltaStateEvent*)eventPtr; 3766 receivedStateFormat = deltaEventPtr->stateFormat; 3767 receivedStateSize = deltaEventPtr->deltaStateSizeInBytes; 3768 ptrToReceivedState = (byte*)deltaEventPtr->deltaState; 3769 offsetInDeviceStateToCopyTo = deltaEventPtr->stateOffset; 3770 3771 // Ignore extra state at end of event. 3772 sizeOfStateToCopy = receivedStateSize; 3773 if (offsetInDeviceStateToCopyTo + sizeOfStateToCopy > stateBlockSizeOfDevice) 3774 { 3775 if (offsetInDeviceStateToCopyTo >= stateBlockSizeOfDevice) 3776 return false; // Entire delta state is out of range. 3777 3778 sizeOfStateToCopy = stateBlockSizeOfDevice - offsetInDeviceStateToCopyTo; 3779 } 3780 } 3781 3782 Debug.Assert(device.m_StateBlock.format == receivedStateFormat, "Received state format does not match format of device"); 3783 3784 // Write state. 3785 return UpdateState(device, updateType, ptrToReceivedState, offsetInDeviceStateToCopyTo, 3786 sizeOfStateToCopy, eventPtr->internalTime, eventPtr); 3787 } 3788 3789 /// <summary> 3790 /// This method is the workhorse for updating input state in the system. It runs all the logic of incorporating 3791 /// new state into devices and triggering whatever change monitors are attached to the state memory that gets 3792 /// touched. 3793 /// </summary> 3794 /// <remarks> 3795 /// This method can be invoked from outside the event processing loop and the given data does not have to come 3796 /// from an event. 3797 /// 3798 /// This method does NOT respect <see cref="IInputStateCallbackReceiver"/>. This means that the device will 3799 /// NOT get a shot at intervening in the state write. 3800 /// </remarks> 3801 /// <param name="device">Device to update state on. <paramref name="stateOffsetInDevice"/> is relative to device's 3802 /// starting offset in memory.</param> 3803 /// <param name="eventPtr">Pointer to state event from which the state change was initiated. Null if the state 3804 /// change is not coming from an event.</param> 3805 internal unsafe bool UpdateState(InputDevice device, InputUpdateType updateType, 3806 void* statePtr, uint stateOffsetInDevice, uint stateSize, double internalTime, InputEventPtr eventPtr = default) 3807 { 3808 var deviceIndex = device.m_DeviceIndex; 3809 ref var stateBlockOfDevice = ref device.m_StateBlock; 3810 3811 ////TODO: limit stateSize and StateOffset by the device's state memory 3812 3813 var deviceBuffer = (byte*)InputStateBuffers.GetFrontBufferForDevice(deviceIndex); 3814 3815 // If state monitors need to be re-sorted, do it now. 3816 // NOTE: This must happen with the monitors in non-signalled state! 3817 SortStateChangeMonitorsIfNecessary(deviceIndex); 3818 3819 // Before we update state, let change monitors compare the old and the new state. 3820 // We do this instead of first updating the front buffer and then comparing to the 3821 // back buffer as that would require a buffer flip for each state change in order 3822 // for the monitors to work reliably. By comparing the *event* data to the current 3823 // state, we can have multiple state events in the same frame yet still get reliable 3824 // change notifications. 3825 var haveSignalledMonitors = 3826 ProcessStateChangeMonitors(deviceIndex, statePtr, 3827 deviceBuffer + stateBlockOfDevice.byteOffset, 3828 stateSize, stateOffsetInDevice); 3829 3830 var deviceStateOffset = device.m_StateBlock.byteOffset + stateOffsetInDevice; 3831 var deviceStatePtr = deviceBuffer + deviceStateOffset; 3832 3833 ////REVIEW: Should we do this only for events but not for InputState.Change()? 3834 // If noise filtering on .current is turned on and the device may have noise, 3835 // determine if the event carries signal or not. 3836 var noiseMask = device.noisy 3837 ? (byte*)InputStateBuffers.s_NoiseMaskBuffer + deviceStateOffset 3838 : null; 3839 // Compare the current state of the device to the newly received state but overlay 3840 // the comparison by the noise mask. 3841 var makeDeviceCurrent = !MemoryHelpers.MemCmpBitRegion(deviceStatePtr, statePtr, 3842 0, stateSize * 8, mask: noiseMask); 3843 3844 // Buffer flip. 3845 var flipped = FlipBuffersForDeviceIfNecessary(device, updateType); 3846 3847 // Now write the state. 3848 #if UNITY_EDITOR 3849 if (updateType == InputUpdateType.Editor) 3850 { 3851 WriteStateChange(m_StateBuffers.m_EditorStateBuffers, deviceIndex, ref stateBlockOfDevice, stateOffsetInDevice, 3852 statePtr, stateSize, flipped); 3853 } 3854 else 3855 #endif 3856 { 3857 WriteStateChange(m_StateBuffers.m_PlayerStateBuffers, deviceIndex, ref stateBlockOfDevice, 3858 stateOffsetInDevice, statePtr, stateSize, flipped); 3859 } 3860 3861 if (makeDeviceCurrent) 3862 { 3863 // Update the pressed/not pressed state of all buttons that have changed this update 3864 // With enough ButtonControls being checked, it's faster to find out which have actually changed rather than test all. 3865 if (InputSystem.s_Manager.m_ReadValueCachingFeatureEnabled || device.m_UseCachePathForButtonPresses) 3866 { 3867 foreach (var button in device.m_UpdatedButtons) 3868 { 3869 #if UNITY_EDITOR 3870 if (updateType == InputUpdateType.Editor) 3871 { 3872 ((ButtonControl)device.allControls[button]).UpdateWasPressedEditor(); 3873 } 3874 else 3875 #endif 3876 ((ButtonControl)device.allControls[button]).UpdateWasPressed(); 3877 } 3878 } 3879 else 3880 { 3881 int buttonCount = 0; 3882 foreach (var button in device.m_ButtonControlsCheckingPressState) 3883 { 3884 #if UNITY_EDITOR 3885 if (updateType == InputUpdateType.Editor) 3886 { 3887 button.UpdateWasPressedEditor(); 3888 } 3889 else 3890 #endif 3891 button.UpdateWasPressed(); 3892 3893 ++buttonCount; 3894 } 3895 3896 // From testing, this is the point at which it becomes more efficient to use the same path as 3897 // ReadValueCaching to work out which ButtonControls have updated, rather than querying all. 3898 if (buttonCount > 45) 3899 device.m_UseCachePathForButtonPresses = true; 3900 } 3901 } 3902 3903 // Notify listeners. 3904 DelegateHelpers.InvokeCallbacksSafe(ref m_DeviceStateChangeListeners, 3905 device, eventPtr, k_InputOnDeviceSettingsChangeMarker, "InputSystem.onDeviceStateChange"); 3906 3907 // Now that we've committed the new state to memory, if any of the change 3908 // monitors fired, let the associated actions know. 3909 if (haveSignalledMonitors) 3910 FireStateChangeNotifications(deviceIndex, internalTime, eventPtr); 3911 3912 return makeDeviceCurrent; 3913 } 3914 3915 private unsafe void WriteStateChange(InputStateBuffers.DoubleBuffers buffers, int deviceIndex, 3916 ref InputStateBlock deviceStateBlock, uint stateOffsetInDevice, void* statePtr, uint stateSizeInBytes, bool flippedBuffers) 3917 { 3918 var frontBuffer = buffers.GetFrontBuffer(deviceIndex); 3919 Debug.Assert(frontBuffer != null); 3920 3921 // If we're updating less than the full state, we need to preserve the parts we are not updating. 3922 // Instead of trying to optimize here and only copy what we really need, we just go and copy the 3923 // entire state of the device over. 3924 // 3925 // NOTE: This copying must only happen once, right after a buffer flip. Otherwise we may copy old, 3926 // stale input state from the back buffer over state that has already been updated with newer 3927 // data. 3928 var deviceStateSize = deviceStateBlock.sizeInBits / 8; // Always byte-aligned; avoid calling alignedSizeInBytes. 3929 if (flippedBuffers && deviceStateSize != stateSizeInBytes) 3930 { 3931 var backBuffer = buffers.GetBackBuffer(deviceIndex); 3932 Debug.Assert(backBuffer != null); 3933 3934 UnsafeUtility.MemCpy( 3935 (byte*)frontBuffer + deviceStateBlock.byteOffset, 3936 (byte*)backBuffer + deviceStateBlock.byteOffset, 3937 deviceStateSize); 3938 } 3939 3940 // If we have enough ButtonControls being checked for wasPressedThisFrame/wasReleasedThisFrame, 3941 // use this path to find out which have actually changed here. 3942 if (InputSystem.s_Manager.m_ReadValueCachingFeatureEnabled || m_Devices[deviceIndex].m_UseCachePathForButtonPresses) 3943 { 3944 // if the buffers have just been flipped, and we're doing a full state update, then the state from the 3945 // previous update is now in the back buffer, and we should be comparing to that when checking what 3946 // controls have changed 3947 var buffer = (byte*)frontBuffer; 3948 if (flippedBuffers && deviceStateSize == stateSizeInBytes) 3949 buffer = (byte*)buffers.GetBackBuffer(deviceIndex); 3950 3951 m_Devices[deviceIndex].WriteChangedControlStates(buffer + deviceStateBlock.byteOffset, statePtr, 3952 stateSizeInBytes, stateOffsetInDevice); 3953 } 3954 3955 UnsafeUtility.MemCpy((byte*)frontBuffer + deviceStateBlock.byteOffset + stateOffsetInDevice, statePtr, 3956 stateSizeInBytes); 3957 } 3958 3959 // Flip front and back buffer for device, if necessary. May flip buffers for more than just 3960 // the given update type. 3961 // Returns true if there was a buffer flip. 3962 private bool FlipBuffersForDeviceIfNecessary(InputDevice device, InputUpdateType updateType) 3963 { 3964 if (updateType == InputUpdateType.BeforeRender) 3965 { 3966 ////REVIEW: I think this is wrong; if we haven't flipped in the current dynamic or fixed update, we should do so now 3967 // We never flip buffers for before render. Instead, we already write 3968 // into the front buffer. 3969 return false; 3970 } 3971 3972#if UNITY_EDITOR 3973 ////REVIEW: should this use the editor update ticks as quasi-frame-boundaries? 3974 // Updates go to the editor only if the game isn't playing or does not have focus. 3975 // Otherwise we fall through to the logic that flips for the *next* dynamic and 3976 // fixed updates. 3977 if (updateType == InputUpdateType.Editor) 3978 { 3979 ////REVIEW: This isn't right. The editor does have update ticks which constitute the equivalent of player frames. 3980 // The editor doesn't really have a concept of frame-to-frame operation the 3981 // same way the player does. So we simply flip buffers on a device whenever 3982 // a new state event for it comes in. 3983 m_StateBuffers.m_EditorStateBuffers.SwapBuffers(device.m_DeviceIndex); 3984 return true; 3985 } 3986#endif 3987 3988 // Flip buffers if we haven't already for this frame. 3989 if (device.m_CurrentUpdateStepCount != InputUpdate.s_UpdateStepCount) 3990 { 3991 m_StateBuffers.m_PlayerStateBuffers.SwapBuffers(device.m_DeviceIndex); 3992 device.m_CurrentUpdateStepCount = InputUpdate.s_UpdateStepCount; 3993 return true; 3994 } 3995 3996 return false; 3997 } 3998 3999 // Domain reload survival logic. Also used for pushing and popping input system 4000 // state for testing. 4001 4002 // Stuff everything that we want to survive a domain reload into 4003 // a m_SerializedState. 4004#if UNITY_EDITOR || DEVELOPMENT_BUILD 4005 [Serializable] 4006 internal struct DeviceState 4007 { 4008 // Preserving InputDevices is somewhat tricky business. Serializing 4009 // them in full would involve pretty nasty work. We have the restriction, 4010 // however, that everything needs to be created from layouts (it partly 4011 // exists for the sake of reload survivability), so we should be able to 4012 // just go and recreate the device from the layout. This also has the 4013 // advantage that if the layout changes between reloads, the change 4014 // automatically takes effect. 4015 public string name; 4016 public string layout; 4017 public string variants; 4018 public string[] usages; 4019 public int deviceId; 4020 public int participantId; 4021 public InputDevice.DeviceFlags flags; 4022 public InputDeviceDescription description; 4023 4024 public void Restore(InputDevice device) 4025 { 4026 var usageCount = usages.LengthSafe(); 4027 for (var i = 0; i < usageCount; ++i) 4028 device.AddDeviceUsage(new InternedString(usages[i])); 4029 device.m_ParticipantId = participantId; 4030 } 4031 } 4032 4033 /// <summary> 4034 /// State we take across domain reloads. 4035 /// </summary> 4036 /// <remarks> 4037 /// Most of the state we re-recreate in-between reloads and do not store 4038 /// in this structure. In particular, we do not preserve anything from 4039 /// the various RegisterXXX(). 4040 /// 4041 /// WARNING 4042 /// 4043 /// Making changes to serialized data format will likely to break upgrading projects from older versions. 4044 /// That is until you restart the editor, then we recreate everything from clean state. 4045 /// </remarks> 4046 [Serializable] 4047 internal struct SerializedState 4048 { 4049 public int layoutRegistrationVersion; 4050 public float pollingFrequency; 4051 public DeviceState[] devices; 4052 public AvailableDevice[] availableDevices; 4053 public InputStateBuffers buffers; 4054 public InputUpdate.SerializedState updateState; 4055 public InputUpdateType updateMask; 4056 public InputSettings.ScrollDeltaBehavior scrollDeltaBehavior; 4057 public InputMetrics metrics; 4058 public InputSettings settings; 4059 public InputActionAsset actions; 4060 4061 #if UNITY_ANALYTICS || UNITY_EDITOR 4062 public bool haveSentStartupAnalytics; 4063 #endif 4064 } 4065 4066 internal SerializedState SaveState() 4067 { 4068 // Devices. 4069 var deviceCount = m_DevicesCount; 4070 var deviceArray = new DeviceState[deviceCount]; 4071 for (var i = 0; i < deviceCount; ++i) 4072 { 4073 var device = m_Devices[i]; 4074 string[] usages = null; 4075 if (device.usages.Count > 0) 4076 usages = device.usages.Select(x => x.ToString()).ToArray(); 4077 4078 var deviceState = new DeviceState 4079 { 4080 name = device.name, 4081 layout = device.layout, 4082 variants = device.variants, 4083 deviceId = device.deviceId, 4084 participantId = device.m_ParticipantId, 4085 usages = usages, 4086 description = device.m_Description, 4087 flags = device.m_DeviceFlags 4088 }; 4089 deviceArray[i] = deviceState; 4090 } 4091 4092 return new SerializedState 4093 { 4094 layoutRegistrationVersion = m_LayoutRegistrationVersion, 4095 pollingFrequency = m_PollingFrequency, 4096 devices = deviceArray, 4097 availableDevices = m_AvailableDevices?.Take(m_AvailableDeviceCount).ToArray(), 4098 buffers = m_StateBuffers, 4099 updateState = InputUpdate.Save(), 4100 updateMask = m_UpdateMask, 4101 scrollDeltaBehavior = m_ScrollDeltaBehavior, 4102 metrics = m_Metrics, 4103 settings = m_Settings, 4104 #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 4105 actions = m_Actions, 4106 #endif 4107 4108 #if UNITY_ANALYTICS || UNITY_EDITOR 4109 haveSentStartupAnalytics = m_HaveSentStartupAnalytics, 4110 #endif 4111 }; 4112 } 4113 4114 internal void RestoreStateWithoutDevices(SerializedState state) 4115 { 4116 m_StateBuffers = state.buffers; 4117 m_LayoutRegistrationVersion = state.layoutRegistrationVersion + 1; 4118 updateMask = state.updateMask; 4119 scrollDeltaBehavior = state.scrollDeltaBehavior; 4120 m_Metrics = state.metrics; 4121 m_PollingFrequency = state.pollingFrequency; 4122 4123 if (m_Settings != null) 4124 Object.DestroyImmediate(m_Settings); 4125 m_Settings = state.settings; 4126 4127 #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 4128 // Note that we just reassign actions and never destroy them since always mapped to persisted asset 4129 // and hence ownership lies with ADB. 4130 m_Actions = state.actions; 4131 #endif 4132 4133 #if UNITY_ANALYTICS || UNITY_EDITOR 4134 m_HaveSentStartupAnalytics = state.haveSentStartupAnalytics; 4135 #endif 4136 4137 ////REVIEW: instead of accessing globals here, we could move this to when we re-create devices 4138 4139 // Update state. 4140 InputUpdate.Restore(state.updateState); 4141 } 4142 4143 // If these are set, we clear them out on the first input update. 4144 internal DeviceState[] m_SavedDeviceStates; 4145 internal AvailableDevice[] m_SavedAvailableDevices; 4146 4147 /// <summary> 4148 /// Recreate devices based on the devices we had before a domain reload. 4149 /// </summary> 4150 /// <remarks> 4151 /// Note that device indices may change between domain reloads. 4152 /// 4153 /// We recreate devices using the layout information as it exists now as opposed to 4154 /// as it existed before the domain reload. This means we'll be picking up any changes that 4155 /// have happened to layouts as part of the reload (including layouts having been removed 4156 /// entirely). 4157 /// </remarks> 4158 internal void RestoreDevicesAfterDomainReload() 4159 { 4160 k_InputRestoreDevicesAfterReloadMarker.Begin(); 4161 4162 using (InputDeviceBuilder.Ref()) 4163 { 4164 DeviceState[] retainedDeviceStates = null; 4165 var deviceStates = m_SavedDeviceStates; 4166 var deviceCount = m_SavedDeviceStates.LengthSafe(); 4167 m_SavedDeviceStates = null; // Prevent layout matcher registering themselves on the fly from picking anything off this list. 4168 for (var i = 0; i < deviceCount; ++i) 4169 { 4170 ref var deviceState = ref deviceStates[i]; 4171 4172 var device = TryGetDeviceById(deviceState.deviceId); 4173 if (device != null) 4174 continue; 4175 4176 var layout = TryFindMatchingControlLayout(ref deviceState.description, 4177 deviceState.deviceId); 4178 if (layout.IsEmpty()) 4179 { 4180 var previousLayout = new InternedString(deviceState.layout); 4181 if (m_Layouts.HasLayout(previousLayout)) 4182 layout = previousLayout; 4183 } 4184 if (layout.IsEmpty() || !RestoreDeviceFromSavedState(ref deviceState, layout)) 4185 ArrayHelpers.Append(ref retainedDeviceStates, deviceState); 4186 } 4187 4188 // See if we can make sense of an available device now that we couldn't make sense of 4189 // before. This can be the case if there's new layout information that wasn't available 4190 // before. 4191 if (m_SavedAvailableDevices != null) 4192 { 4193 m_AvailableDevices = m_SavedAvailableDevices; 4194 m_AvailableDeviceCount = m_SavedAvailableDevices.LengthSafe(); 4195 for (var i = 0; i < m_AvailableDeviceCount; ++i) 4196 { 4197 var device = TryGetDeviceById(m_AvailableDevices[i].deviceId); 4198 if (device != null) 4199 continue; 4200 4201 if (m_AvailableDevices[i].isRemoved) 4202 continue; 4203 4204 var layout = TryFindMatchingControlLayout(ref m_AvailableDevices[i].description, 4205 m_AvailableDevices[i].deviceId); 4206 if (!layout.IsEmpty()) 4207 { 4208 try 4209 { 4210 AddDevice(layout, m_AvailableDevices[i].deviceId, 4211 deviceDescription: m_AvailableDevices[i].description, 4212 deviceFlags: m_AvailableDevices[i].isNative ? InputDevice.DeviceFlags.Native : 0); 4213 } 4214 catch (Exception) 4215 { 4216 // Just ignore. Simply means we still can't really turn the device into something useful. 4217 } 4218 } 4219 } 4220 } 4221 4222 // Done. Discard saved arrays. 4223 m_SavedDeviceStates = retainedDeviceStates; 4224 m_SavedAvailableDevices = null; 4225 } 4226 4227 k_InputRestoreDevicesAfterReloadMarker.End(); 4228 } 4229 4230 // We have two general types of devices we need to care about when recreating devices 4231 // after domain reloads: 4232 // 4233 // A) device with InputDeviceDescription 4234 // B) device created directly from specific layout 4235 // 4236 // A) should go through the normal matching process whereas B) should get recreated with 4237 // layout of same name (if still available). 4238 // 4239 // So we kick device recreation off from two points: 4240 // 4241 // 1) From RegisterControlLayoutMatcher to catch A) 4242 // 2) From RegisterControlLayout to catch B) 4243 // 4244 // Additionally, we have the complication that a layout a device was using was something 4245 // dynamically registered from onFindLayoutForDevice. We don't do anything special about that. 4246 // The first full input update will flush out the list of saved device states and at that 4247 // point, any onFindLayoutForDevice hooks simply have to be in place. If they are, devices 4248 // will get recreated appropriately. 4249 // 4250 // It would be much simpler to recreate all devices as the first thing in the first full input 4251 // update but that would mean that devices would become available only very late. They would 4252 // not, for example, be available when MonoBehaviour.Start methods are invoked. 4253 4254 private bool RestoreDeviceFromSavedState(ref DeviceState deviceState, InternedString layout) 4255 { 4256 // We assign the same device IDs here to newly created devices that they had 4257 // before the domain reload. This is safe as device ID allocation is under the 4258 // control of the runtime and not expected to be affected by a domain reload. 4259 4260 InputDevice device; 4261 try 4262 { 4263 device = AddDevice(layout, 4264 deviceDescription: deviceState.description, 4265 deviceId: deviceState.deviceId, 4266 deviceName: deviceState.name, 4267 deviceFlags: deviceState.flags, 4268 variants: new InternedString(deviceState.variants)); 4269 } 4270 catch (Exception exception) 4271 { 4272 Debug.LogError( 4273 $"Could not recreate input device '{deviceState.description}' with layout '{deviceState.layout}' and variants '{deviceState.variants}' after domain reload"); 4274 Debug.LogException(exception); 4275 return true; // Don't try again. 4276 } 4277 4278 deviceState.Restore(device); 4279 4280 return true; 4281 } 4282 4283#endif // UNITY_EDITOR || DEVELOPMENT_BUILD 4284 } 4285}