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}