A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Runtime.InteropServices;
4using UnityEngine.InputSystem.LowLevel;
5using UnityEngine.InputSystem.Utilities;
6using Unity.Collections.LowLevel.Unsafe;
7using UnityEngine.InputSystem.Controls;
8using UnityEngine.InputSystem.Layouts;
9
10////TODO: runtime remapping of control usages on a per-device basis
11
12////TODO: finer-grained control over what devices deliver input while running in background
13//// (e.g. get gamepad input but do *not* get mouse and keyboard input)
14
15////REVIEW: should be possible to completely hijack the input stream of a device such that its original input is suppressed
16
17////REVIEW: can we construct the control tree of devices on demand so that the user never has to pay for
18//// the heap objects of devices that aren't used?
19
20// per device functions:
21// - update/poll
22// - IOCTL
23// - text input
24// - configuration change
25// - make current
26// - on remove (also resets current)
27//
28// Ideally, these would *not* be virtual methods on InputDevice but use a different process (which?)
29// for associating responses with devices
30
31namespace UnityEngine.InputSystem
32{
33 /// <summary>
34 /// Represents an input device which is always the root of a hierarchy of <see cref="InputControl"/> instances.
35 /// </summary>
36 /// <remarks>
37 /// Input devices act as the container for control hierarchies. Every hierarchy has to have
38 /// a device at the root. Devices cannot occur as children of other controls.
39 ///
40 /// Devices are usually created automatically in response to hardware being discovered by the Unity
41 /// runtime. However, it is possible to manually add devices using methods such as <see
42 /// cref="InputSystem.AddDevice{TDevice}(string)"/>.
43 ///
44 /// <example>
45 /// <code>
46 /// // Add a "synthetic" gamepad that isn't actually backed by hardware.
47 /// var gamepad = InputSystem.AddDevice<Gamepad>();
48 /// </code>
49 /// </example>
50 ///
51 /// There are subclasses representing the most common types of devices, like <see cref="Mouse"/>,
52 /// <see cref="Keyboard"/>, <see cref="Gamepad"/>, and <see cref="Touchscreen"/>.
53 ///
54 /// To create your own types of devices, you can derive from InputDevice and register your device
55 /// as a new "layout".
56 ///
57 /// <example>
58 /// <code>
59 /// // InputControlLayoutAttribute attribute is only necessary if you want
60 /// // to override default behavior that occurs when registering your device
61 /// // as a layout.
62 /// // The most common use of InputControlLayoutAttribute is to direct the system
63 /// // to a custom "state struct" through the `stateType` property. See below for details.
64 /// [InputControlLayout(displayName = "My Device", stateType = typeof(MyDeviceState))]
65 /// #if UNITY_EDITOR
66 /// [InitializeOnLoad]
67 /// #endif
68 /// public class MyDevice : InputDevice
69 /// {
70 /// public ButtonControl button { get; private set; }
71 /// public AxisControl axis { get; private set; }
72 ///
73 /// // Register the device.
74 /// static MyDevice()
75 /// {
76 /// // In case you want instance of your device to automatically be created
77 /// // when specific hardware is detected by the Unity runtime, you have to
78 /// // add one or more "device matchers" (InputDeviceMatcher) for the layout.
79 /// // These matchers are compared to an InputDeviceDescription received from
80 /// // the Unity runtime when a device is connected. You can add them either
81 /// // using InputSystem.RegisterLayoutMatcher() or by directly specifying a
82 /// // matcher when registering the layout.
83 /// InputSystem.RegisterLayout<MyDevice>(
84 /// // For the sake of demonstration, let's assume your device is a HID
85 /// // and you want to match by PID and VID.
86 /// matches: new InputDeviceMatcher()
87 /// .WithInterface("HID")
88 /// .WithCapability("PID", 1234)
89 /// .WithCapability("VID", 5678));
90 /// }
91 ///
92 /// // This is only to trigger the static class constructor to automatically run
93 /// // in the player.
94 /// [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
95 /// private static void InitializeInPlayer() {}
96 ///
97 /// protected override void FinishSetup()
98 /// {
99 /// base.FinishSetup();
100 /// button = GetChildControl<ButtonControl>("button");
101 /// axis = GetChildControl<AxisControl>("axis");
102 /// }
103 /// }
104 ///
105 /// // A "state struct" describes the memory format used by a device. Each device can
106 /// // receive and store memory in its custom format. InputControls are then connected
107 /// // the individual pieces of memory and read out values from them.
108 /// [StructLayout(LayoutKind.Explicit, Size = 32)]
109 /// public struct MyDeviceState : IInputStateTypeInfo
110 /// {
111 /// // In the case of a HID (which we assume for the sake of this demonstration),
112 /// // the format will be "HID". In practice, the format will depend on how your
113 /// // particular device is connected and fed into the input system.
114 /// // The format is a simple FourCC code that "tags" state memory blocks for the
115 /// // device to give a base level of safety checks on memory operations.
116 /// public FourCC format => return new FourCC('H', 'I', 'D');
117 ///
118 /// // InputControlAttributes on fields tell the input system to create controls
119 /// // for the public fields found in the struct.
120 ///
121 /// // Assume a 16bit field of buttons. Create one button that is tied to
122 /// // bit #3 (zero-based). Note that buttons do not need to be stored as bits.
123 /// // They can also be stored as floats or shorts, for example.
124 /// [InputControl(name = "button", layout = "Button", bit = 3)]
125 /// public ushort buttons;
126 ///
127 /// // Create a floating-point axis. The name, if not supplied, is taken from
128 /// // the field.
129 /// [InputControl(layout = "Axis")]
130 /// public short axis;
131 /// }
132 /// </code>
133 /// </example>
134 ///
135 /// Devices can have usages like any other control (<see cref="InputControl.usages"/>). Unlike other controls,
136 /// however, usages of InputDevices are allowed to be changed on the fly without requiring a change to the
137 /// device layout (see <see cref="InputSystem.SetDeviceUsage(InputDevice,string)"/>).
138 ///
139 /// For a more complete example of how to implement custom input devices, check out the "Custom Device"
140 /// sample which you can install from the Unity package manager.
141 ///
142 /// And, as always, you can also find more information in the <a href="../manual/Devices.html">manual</a>.
143 /// </remarks>
144 /// <seealso cref="InputControl"/>
145 /// <seealso cref="Mouse"/>
146 /// <seealso cref="Keyboard"/>
147 /// <seealso cref="Gamepad"/>
148 /// <seealso cref="Touchscreen"/>
149 public class InputDevice : InputControl
150 {
151 /// <summary>
152 /// Value of an invalid <see cref="deviceId"/>.
153 /// </summary>
154 /// <remarks>
155 /// The input system will not assigned this ID to any device.
156 /// </remarks>
157 public const int InvalidDeviceId = 0;
158
159 internal const int kLocalParticipantId = 0;
160 internal const int kInvalidDeviceIndex = -1;
161
162 /// <summary>
163 /// Metadata describing the device (product name etc.).
164 /// </summary>
165 /// <remarks>
166 /// The description of a device is unchanging over its lifetime and does not
167 /// comprise data about a device's configuration (which is considered mutable).
168 ///
169 /// In most cases, the description for a device is supplied by the Unity runtime.
170 /// This it the case for all <see cref="native"/> input devices. However, it is
171 /// also possible to inject new devices in the form of device descriptions into
172 /// the system using <see cref="InputSystem.AddDevice(InputDeviceDescription)"/>.
173 ///
174 /// The description of a device is what is matched by an <see cref="InputDeviceMatcher"/>
175 /// to find the <see cref="InputControl.layout"/> to use for a device.
176 /// </remarks>
177 public InputDeviceDescription description => m_Description;
178
179 ////REVIEW: When we can break the API, probably makes sense to replace this single bool with one for sending and one for receiving events
180 /// <summary>
181 /// Whether the device is currently enabled (that is, sends and receives events).
182 /// </summary>
183 /// <remarks>
184 /// A device that is disabled will not receive events. I.e. events that are being sent to the device
185 /// will be ignored.
186 ///
187 /// When disabling a <see cref="native"/> device, a <see cref="DisableDeviceCommand">disable command</see> will
188 /// also be sent to the <see cref="IInputRuntime">runtime</see>. It depends on the specific runtime whether the
189 /// device command is supported but if it is, the device will be disabled in the runtime and no longer send
190 /// events. This is especially important for devices such as <see cref="Sensor">sensors</see> that incur both
191 /// computation and battery consumption overhead while enabled.
192 ///
193 /// Specific types of devices can choose to start out in disabled state by default. This is generally the
194 /// case for <see cref="Sensor">sensors</see> to ensure that their overhead is only incurred when actually
195 /// being used by the application.
196 /// </remarks>
197 /// <seealso cref="InputSystem.EnableDevice"/>
198 /// <seealso cref="InputSystem.DisableDevice"/>
199 public bool enabled
200 {
201 get
202 {
203 #if UNITY_EDITOR
204 if (InputState.currentUpdateType == InputUpdateType.Editor && (m_DeviceFlags & DeviceFlags.DisabledWhileInBackground) != 0)
205 return true;
206 #endif
207
208 if ((m_DeviceFlags & (DeviceFlags.DisabledInFrontend | DeviceFlags.DisabledWhileInBackground)) != 0)
209 return false;
210
211 return QueryEnabledStateFromRuntime();
212 }
213 }
214
215 ////TODO: rename this to canReceiveInputInBackground (once we can break API)
216 /// <summary>
217 /// If true, the device is capable of delivering input while the application is running in the background, i.e.
218 /// while <c>Application.isFocused</c> is false.
219 /// </summary>
220 /// <value>Whether the device can generate input while in the background.</value>
221 /// <remarks>
222 /// The value of this property is determined by three separator factors.
223 ///
224 /// For one, <see cref="native"/> devices have an inherent value for this property that can be retrieved through
225 /// <see cref="QueryCanRunInBackground"/>. This determines whether at the input collection level, the device is
226 /// capable of producing input independent of application. This is rare and only a select set of hardware, platform,
227 /// and SDK/API combinations support this. The prominent class of input devices that in general do support this
228 /// behavior are VR devices.
229 ///
230 /// Furthermore, the property may be force-set through a device's <see cref="InputControl.layout"/> by
231 /// means of <see cref="InputControlLayout.canRunInBackground"/>.
232 ///
233 /// Lastly, in the editor, the value of the property may be overridden depending on <see cref="InputSettings.editorInputBehaviorInPlayMode"/>
234 /// in case certain devices are automatically kept running in play mode even when no Game View has focus.
235 ///
236 /// Be aware that as far as players are concerned, only certain platforms support running Unity while not having focus.
237 /// On mobile platforms, for example, this is generally not supported. In this case, the value of this property
238 /// has no impact on input while the application does not have focus. See <see cref="InputSettings.backgroundBehavior"/>
239 /// for more details.
240 /// </remarks>
241 /// <seealso cref="InputSettings.backgroundBehavior"/>
242 /// <seealso cref="InputControlLayout.canRunInBackground"/>
243 public bool canRunInBackground
244 {
245 get
246 {
247 // In the editor, "background" refers to "game view not focused", not to the editor not being active.
248 // So, we modulate canRunInBackground depending on how input should behave WRT game view according
249 // to the input settings.
250 #if UNITY_EDITOR
251 var gameViewFocus = InputSystem.settings.editorInputBehaviorInPlayMode;
252 if (gameViewFocus == InputSettings.EditorInputBehaviorInPlayMode.AllDevicesRespectGameViewFocus)
253 return false; // No device considered being able to run without game view focus.
254 if (gameViewFocus == InputSettings.EditorInputBehaviorInPlayMode.PointersAndKeyboardsRespectGameViewFocus)
255 return !(this is Pointer || this is Keyboard); // Anything but pointers and keyboards considered as being able to run in background.
256 #endif
257
258 return canDeviceRunInBackground;
259 }
260 }
261 /// <summary>
262 /// In editor, it may differ from canRunInBackground depending on the gameViewFocus setting.
263 /// This property is used by Device Debug View
264 /// </summary>
265 /// <value>Whether the device should generate input while in the background.</value>
266 internal bool canDeviceRunInBackground
267 {
268 get
269 {
270 if ((m_DeviceFlags & DeviceFlags.CanRunInBackgroundHasBeenQueried) != 0)
271 return (m_DeviceFlags & DeviceFlags.CanRunInBackground) != 0;
272
273 var command = QueryCanRunInBackground.Create();
274 m_DeviceFlags |= DeviceFlags.CanRunInBackgroundHasBeenQueried;
275 if (ExecuteCommand(ref command) >= 0 && command.canRunInBackground)
276 {
277 m_DeviceFlags |= DeviceFlags.CanRunInBackground;
278 return true;
279 }
280
281 m_DeviceFlags &= ~DeviceFlags.CanRunInBackground;
282 return false;
283 }
284 }
285
286 /// <summary>
287 /// Whether the device has been added to the system.
288 /// </summary>
289 /// <value>If true, the device is currently among the devices in <see cref="InputSystem.devices"/>.</value>
290 /// <remarks>
291 /// Devices may be removed at any time. Either when their hardware is unplugged or when they
292 /// are manually removed through <see cref="InputSystem.RemoveDevice"/> or by being excluded
293 /// through <see cref="InputSettings.supportedDevices"/>. When a device is removed, its instance,
294 /// however, will not disappear. This property can be used to check whether the device is part
295 /// of the current set of active devices.
296 /// </remarks>
297 /// <seealso cref="InputSystem.devices"/>
298 public bool added => m_DeviceIndex != kInvalidDeviceIndex;
299
300 /// <summary>
301 /// Whether the device is mirrored from a remote input system and not actually present
302 /// as a "real" device in the local system.
303 /// </summary>
304 /// <value>Whether the device mirrors a device from a remotely connected input system.</value>
305 /// <seealso cref="InputSystem.remoting"/>
306 /// <seealso cref="InputRemoting"/>
307 public bool remote => (m_DeviceFlags & DeviceFlags.Remote) == DeviceFlags.Remote;
308
309 /// <summary>
310 /// Whether the device comes from the <see cref="IInputRuntime">runtime</see>
311 /// </summary>
312 /// <value>Whether the device has been discovered by the Unity runtime.</value>
313 /// <remarks>
314 /// Devices can be discovered when <see cref="IInputRuntime.onDeviceDiscovered">reported</see>
315 /// by the runtime or they can be added manually through the various <see cref="InputSystem.AddDevice(InputDevice)">
316 /// AddDevice</see> APIs. Devices reported by the runtime will return true for this
317 /// property whereas devices added manually will return false.
318 ///
319 /// Devices reported by the runtime will usually come from the Unity engine itself.
320 /// </remarks>
321 /// <seealso cref="IInputRuntime"/>
322 /// <seealso cref="IInputRuntime.onDeviceDiscovered"/>
323 public bool native => (m_DeviceFlags & DeviceFlags.Native) == DeviceFlags.Native;
324
325 /// <summary>
326 /// Whether the device requires an extra update before rendering.
327 /// </summary>
328 /// <remarks>
329 /// The value of this property is determined by <see cref="InputControlLayout.updateBeforeRender"/> in
330 /// the device's <see cref="InputControlLayout">control layout</see>.
331 ///
332 /// The extra update is necessary for tracking devices that are used in rendering code. For example,
333 /// the eye transforms of an HMD should be refreshed right before rendering as refreshing only in the
334 /// beginning of the frame will lead to a noticeable lag.
335 /// </remarks>
336 /// <seealso cref="InputUpdateType.BeforeRender"/>
337 public bool updateBeforeRender => (m_DeviceFlags & DeviceFlags.UpdateBeforeRender) == DeviceFlags.UpdateBeforeRender;
338
339 /// <summary>
340 /// Unique numeric ID for the device.
341 /// </summary>
342 /// <remarks>
343 /// This is only assigned once a device has been added to the system. No two devices will receive the same
344 /// ID and no device will receive an ID that another device used before even if the device was removed. The
345 /// only exception to this is if a device gets re-created as part of a layout change. For example, if a new
346 /// layout is registered that replaces the <see cref="Mouse"/> layout, all <see cref="Mouse"/> devices will
347 /// get recreated but will keep their existing device IDs.
348 ///
349 /// IDs are assigned by the input runtime.
350 /// </remarks>
351 /// <seealso cref="IInputRuntime.AllocateDeviceId"/>
352 public int deviceId => m_DeviceId;
353
354 /// <summary>
355 /// Timestamp of last state event used to update the device.
356 /// </summary>
357 /// <remarks>
358 /// Events other than <see cref="LowLevel.StateEvent"/> and <see cref="LowLevel.DeltaStateEvent"/> will
359 /// not cause lastUpdateTime to be changed.
360 /// The "timeline" is reset to 0 when entering play mode. If there are any events incoming or device
361 /// updates which occur prior to entering play mode, these will appear negative.
362 /// </remarks>
363 public double lastUpdateTime => m_LastUpdateTimeInternal - InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup;
364
365 public bool wasUpdatedThisFrame => m_CurrentUpdateStepCount == InputUpdate.s_UpdateStepCount;
366
367 /// <summary>
368 /// A flattened list of controls that make up the device.
369 /// </summary>
370 /// <remarks>
371 /// Does not allocate.
372 /// </remarks>
373 public ReadOnlyArray<InputControl> allControls =>
374 // Since m_ChildrenForEachControl contains the device's children as well as the children
375 // of each control in the hierarchy, and since each control can only have a single parent,
376 // this list will actually deliver a flattened list of all controls in the hierarchy (and without
377 // the device itself being listed).
378 new ReadOnlyArray<InputControl>(m_ChildrenForEachControl);
379
380 ////REVIEW: This violates the constraint of controls being required to not have reference types as value types.
381 /// <inheritdoc/>
382 public override Type valueType => typeof(byte[]);
383
384 /// <inheritdoc/>
385 public override int valueSizeInBytes => (int)m_StateBlock.alignedSizeInBytes;
386
387 // This one just leads to confusion as you can access it from subclasses and then be surprised
388 // that it doesn't only include members of those classes.
389 [Obsolete("Use 'InputSystem.devices' instead. (UnityUpgradable) -> InputSystem.devices", error: false)]
390 public static ReadOnlyArray<InputDevice> all => InputSystem.devices;
391
392 /// <summary>
393 /// This constructor is public for the sake of <c>Activator.CreateInstance</c> only. To construct
394 /// devices, use methods such as <see cref="InputSystem.AddDevice{TDevice}(string)"/>. Manually
395 /// using <c>new</c> on InputDevice will not result in a usable device.
396 /// </summary>
397 public InputDevice()
398 {
399 m_DeviceId = InvalidDeviceId;
400 m_ParticipantId = kLocalParticipantId;
401 m_DeviceIndex = kInvalidDeviceIndex;
402 }
403
404 ////REVIEW: Is making devices be byte[] values really all that useful? Seems better than returning nulls but
405 //// at the same time, seems questionable.
406
407 /// <inheritdoc/>
408 public override unsafe object ReadValueFromBufferAsObject(void* buffer, int bufferSize)
409 {
410 throw new NotImplementedException();
411 }
412
413 /// <inheritdoc/>
414 public override unsafe object ReadValueFromStateAsObject(void* statePtr)
415 {
416 if (m_DeviceIndex == kInvalidDeviceIndex)
417 return null;
418
419 var numBytes = stateBlock.alignedSizeInBytes;
420 var array = new byte[numBytes];
421 fixed(byte* arrayPtr = array)
422 {
423 var adjustedStatePtr = (byte*)statePtr + m_StateBlock.byteOffset;
424 UnsafeUtility.MemCpy(arrayPtr, adjustedStatePtr, numBytes);
425 }
426
427 return array;
428 }
429
430 /// <inheritdoc/>
431 public override unsafe void ReadValueFromStateIntoBuffer(void* statePtr, void* bufferPtr, int bufferSize)
432 {
433 if (statePtr == null)
434 throw new ArgumentNullException(nameof(statePtr));
435 if (bufferPtr == null)
436 throw new ArgumentNullException(nameof(bufferPtr));
437 if (bufferSize < valueSizeInBytes)
438 throw new ArgumentException($"Buffer too small (expected: {valueSizeInBytes}, actual: {bufferSize}");
439
440 var adjustedStatePtr = (byte*)statePtr + m_StateBlock.byteOffset;
441 UnsafeUtility.MemCpy(bufferPtr, adjustedStatePtr, m_StateBlock.alignedSizeInBytes);
442 }
443
444 /// <inheritdoc/>
445 public override unsafe bool CompareValue(void* firstStatePtr, void* secondStatePtr)
446 {
447 if (firstStatePtr == null)
448 throw new ArgumentNullException(nameof(firstStatePtr));
449 if (secondStatePtr == null)
450 throw new ArgumentNullException(nameof(secondStatePtr));
451
452 var adjustedFirstStatePtr = (byte*)firstStatePtr + m_StateBlock.byteOffset;
453 var adjustedSecondStatePtr = (byte*)firstStatePtr + m_StateBlock.byteOffset;
454
455 return UnsafeUtility.MemCmp(adjustedFirstStatePtr, adjustedSecondStatePtr,
456 m_StateBlock.alignedSizeInBytes) == 0;
457 }
458
459 /// <summary>
460 /// Called by the system when the configuration of the device has changed.
461 /// </summary>
462 /// <seealso cref="DeviceConfigurationEvent"/>
463 internal void NotifyConfigurationChanged()
464 {
465 // Mark all controls in the hierarchy as having their config out of date.
466 // We don't want to update configuration right away but rather wait until
467 // someone actually depends on it.
468 isConfigUpToDate = false;
469 for (var i = 0; i < m_ChildrenForEachControl.Length; ++i)
470 m_ChildrenForEachControl[i].isConfigUpToDate = false;
471
472 // Make sure we fetch the enabled/disabled state again.
473 m_DeviceFlags &= ~DeviceFlags.DisabledStateHasBeenQueriedFromRuntime;
474
475 OnConfigurationChanged();
476 }
477
478 /// <summary>
479 /// Make this the current device of its type.
480 /// </summary>
481 /// <remarks>
482 /// This method is called automatically by the input system when a device is
483 /// added or when input is received on it. Many types of devices have <c>.current</c>
484 /// getters that allow querying the last used device of a specific type directly (for
485 /// example, see <see cref="Gamepad.current"/>).
486 ///
487 /// There is one special case, however, related to noise. A device that has noisy controls
488 /// (i.e. controls for which <see cref="InputControl.noisy"/> is true) may receive input events
489 /// that contain no meaningful user interaction but are simply just noise from the device. A
490 /// good example of this is the PS4 gamepad which has a built-in gyro and may thus constantly
491 /// feed events into the input system even if not being actually in use. If, for example, an
492 /// Xbox gamepad and PS4 gamepad are both connected to a PC and the user is playing with the
493 /// Xbox gamepad, the PS4 gamepad would still constantly make itself <see cref="Gamepad.current"/>
494 /// by simply flooding the system with events. Hence why by default, noise on <c>.current</c> getters
495 /// will be filtered out and a device will only see <c>MakeCurrent</c> getting called if their input
496 /// was detected on non-noisy controls.
497 /// </remarks>
498 /// <seealso cref="Pointer.current"/>
499 /// <seealso cref="Gamepad.current"/>
500 /// <seealso cref="Mouse.current"/>
501 /// <seealso cref="Pen.current"/>
502 public virtual void MakeCurrent()
503 {
504 }
505
506 /// <summary>
507 /// Called by the system when the device is added to <see cref="InputSystem.devices"/>.
508 /// </summary>
509 /// <remarks>
510 /// This is called <em>after</em> the device has already been added.
511 /// </remarks>
512 /// <seealso cref="InputSystem.devices"/>
513 /// <seealso cref="InputDeviceChange.Added"/>
514 /// <seealso cref="OnRemoved"/>
515 protected virtual void OnAdded()
516 {
517 }
518
519 /// <summary>
520 /// Called by the system when the device is removed from <see cref="InputSystem.devices"/>.
521 /// </summary>
522 /// <remarks>
523 /// This is called <em>after</em> the device has already been removed.
524 /// </remarks>
525 /// <seealso cref="InputSystem.devices"/>
526 /// <seealso cref="InputDeviceChange.Removed"/>
527 /// <seealso cref="OnRemoved"/>
528 protected virtual void OnRemoved()
529 {
530 }
531
532 /// <summary>
533 /// Called by the system when the device configuration is changed. This happens when the backend sends
534 /// a <see cref="DeviceConfigurationEvent"/> for the device.
535 /// </summary>
536 /// <remarks>
537 /// This method can be used to flush out cached information. An example of where this happens is <see cref="Controls.KeyControl"/>
538 /// caching information about the display name of a control. As this depends on the current keyboard layout, the information
539 /// has to be fetched dynamically (this happens using <see cref="QueryKeyNameCommand"/>). Whenever the keyboard layout changes,
540 /// the system sends a <see cref="DeviceConfigurationEvent"/> for the <see cref="Keyboard"/> at which point the device flushes
541 /// all cached key names.
542 /// </remarks>
543 /// <seealso cref="InputManager.OnUpdate"/>
544 /// <seealso cref="InputDeviceChange.ConfigurationChanged"/>
545 /// <seealso cref="OnConfigurationChanged"/>///
546 protected virtual void OnConfigurationChanged()
547 {
548 }
549
550 ////TODO: add overridable OnDisable/OnEnable that fire the device commands
551
552 ////REVIEW: return just bool instead of long and require everything else to go in the command?
553 /// <summary>
554 /// Perform a device-specific command.
555 /// </summary>
556 /// <param name="command">Data for the command to be performed.</param>
557 /// <returns>A transfer-specific return code. Negative values are considered failure codes.</returns>
558 /// <remarks>
559 /// Commands allow devices to set up custom protocols without having to extend
560 /// the device API. This is most useful for devices implemented in the native Unity runtime
561 /// which, through the command interface, may provide custom, device-specific functions.
562 ///
563 /// This is a low-level API. It works in a similar way to <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa363216%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396" target="_blank">
564 /// DeviceIoControl</a> on Windows and <a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/ioctl.2.html#//apple_ref/doc/man/2/ioctl" target="_blank">ioctl</a>
565 /// on UNIX-like systems.
566 /// </remarks>
567 public unsafe long ExecuteCommand<TCommand>(ref TCommand command)
568 where TCommand : struct, IInputDeviceCommandInfo
569 {
570 var commandPtr = (InputDeviceCommand*)UnsafeUtility.AddressOf(ref command);
571
572 // Give callbacks first shot.
573 var manager = InputSystem.s_Manager;
574 manager.m_DeviceCommandCallbacks.LockForChanges();
575 for (var i = 0; i < manager.m_DeviceCommandCallbacks.length; ++i)
576 {
577 try
578 {
579 var result = manager.m_DeviceCommandCallbacks[i](this, commandPtr);
580 if (result.HasValue)
581 return result.Value;
582 }
583 catch (Exception exception)
584 {
585 Debug.LogError($"{exception.GetType().Name} while executing 'InputSystem.onDeviceCommand' callbacks");
586 Debug.LogException(exception);
587 }
588 }
589 manager.m_DeviceCommandCallbacks.UnlockForChanges();
590
591 return ExecuteCommand((InputDeviceCommand*)UnsafeUtility.AddressOf(ref command));
592 }
593
594 protected virtual unsafe long ExecuteCommand(InputDeviceCommand* commandPtr)
595 {
596 return InputRuntime.s_Instance.DeviceCommand(deviceId, commandPtr);
597 }
598
599 internal bool QueryEnabledStateFromRuntime()
600 {
601 // Fetch state from runtime, if necessary.
602 if ((m_DeviceFlags & DeviceFlags.DisabledStateHasBeenQueriedFromRuntime) == 0)
603 {
604 var command = QueryEnabledStateCommand.Create();
605 if (ExecuteCommand(ref command) >= 0)
606 {
607 if (command.isEnabled)
608 m_DeviceFlags &= ~DeviceFlags.DisabledInRuntime;
609 else
610 m_DeviceFlags |= DeviceFlags.DisabledInRuntime;
611 }
612 else
613 {
614 // We got no response on the enable/disable state. Assume device is enabled.
615 m_DeviceFlags &= ~DeviceFlags.DisabledInRuntime;
616 }
617
618 // Only fetch enable/disable state again if we get a configuration change event.
619 m_DeviceFlags |= DeviceFlags.DisabledStateHasBeenQueriedFromRuntime;
620 }
621
622 return (m_DeviceFlags & DeviceFlags.DisabledInRuntime) == 0;
623 }
624
625 [Serializable]
626 [Flags]
627 internal enum DeviceFlags
628 {
629 UpdateBeforeRender = 1 << 0,
630
631 HasStateCallbacks = 1 << 1,
632 HasControlsWithDefaultState = 1 << 2,
633 HasDontResetControls = 1 << 10,
634 HasEventMerger = 1 << 13,
635 HasEventPreProcessor = 1 << 14,
636
637 Remote = 1 << 3, // It's a local mirror of a device from a remote player connection.
638 Native = 1 << 4, // It's a device created from data surfaced by NativeInputRuntime.
639
640 DisabledInFrontend = 1 << 5, // Explicitly disabled on the managed side.
641 DisabledInRuntime = 1 << 7, // Disabled in the native runtime.
642 DisabledWhileInBackground = 1 << 8, // Disabled while the player is running in the background.
643 DisabledStateHasBeenQueriedFromRuntime = 1 << 6, // Whether we have fetched the current enable/disable state from the runtime.
644
645 CanRunInBackground = 1 << 11,
646 CanRunInBackgroundHasBeenQueried = 1 << 12,
647 }
648
649 internal bool disabledInFrontend
650 {
651 get => (m_DeviceFlags & DeviceFlags.DisabledInFrontend) != 0;
652 set
653 {
654 if (value)
655 m_DeviceFlags |= DeviceFlags.DisabledInFrontend;
656 else
657 m_DeviceFlags &= ~DeviceFlags.DisabledInFrontend;
658 }
659 }
660
661 internal bool disabledInRuntime
662 {
663 get => (m_DeviceFlags & DeviceFlags.DisabledInRuntime) != 0;
664 set
665 {
666 if (value)
667 m_DeviceFlags |= DeviceFlags.DisabledInRuntime;
668 else
669 m_DeviceFlags &= ~DeviceFlags.DisabledInRuntime;
670 }
671 }
672
673 internal bool disabledWhileInBackground
674 {
675 get => (m_DeviceFlags & DeviceFlags.DisabledWhileInBackground) != 0;
676 set
677 {
678 if (value)
679 m_DeviceFlags |= DeviceFlags.DisabledWhileInBackground;
680 else
681 m_DeviceFlags &= ~DeviceFlags.DisabledWhileInBackground;
682 }
683 }
684
685 internal DeviceFlags m_DeviceFlags;
686 internal int m_DeviceId;
687 internal int m_ParticipantId;
688 // Index in InputManager.m_Devices.
689 internal int m_DeviceIndex;
690 // Amount of bytes processed in the current update step. Used only for logging purposes.
691 internal uint m_CurrentProcessedEventBytesOnUpdate;
692 internal InputDeviceDescription m_Description;
693
694 /// <summary>
695 /// Timestamp of last event we received.
696 /// </summary>
697 /// <seealso cref="LowLevel.InputEvent.time"/>
698 internal double m_LastUpdateTimeInternal;
699
700 // Update count corresponding to the current front buffers that are active on the device.
701 // We use this to know when to flip buffers.
702 internal uint m_CurrentUpdateStepCount;
703
704 // List of aliases for all controls. Each control gets a slice of this array.
705 // See 'InputControl.aliases'.
706 // NOTE: The device's own aliases are part of this array as well.
707 internal InternedString[] m_AliasesForEachControl;
708
709 // List of usages for all controls. Each control gets a slice of this array.
710 // See 'InputControl.usages'.
711 // NOTE: The device's own usages are part of this array as well. They are always
712 // at the *end* of the array.
713 internal InternedString[] m_UsagesForEachControl;
714 // This one does NOT contain the device itself, i.e. it only contains controls on the device
715 // and may this be shorter than m_UsagesForEachControl.
716 internal InputControl[] m_UsageToControl;
717
718 // List of children for all controls. Each control gets a slice of this array.
719 // See 'InputControl.children'.
720 // NOTE: The device's own children are part of this array as well.
721 internal InputControl[] m_ChildrenForEachControl;
722
723 // Used with value caching to track updated button press states.
724 internal HashSet<int> m_UpdatedButtons;
725
726 // Used for updating button press states when we don't take the value caching path.
727 internal List<ButtonControl> m_ButtonControlsCheckingPressState;
728
729 // Once we hit about 45 ButtonControls being queried for wasPressedThisFrame/wasReleasedThisFrame, mark as such
730 // so that we can take the ReadValueCaching path for more efficient updating.
731 internal bool m_UseCachePathForButtonPresses = false;
732
733 // An ordered list of ints each containing a bit offset into the state of the device (*without* the added global
734 // offset), a bit count for the size of the state of the control, and an associated index into m_ChildrenForEachControl
735 // for the corresponding control.
736 // NOTE: This contains *leaf* controls only.
737 internal uint[] m_StateOffsetToControlMap;
738
739 // Holds the nodes that represent the tree of memory ranges that each control occupies. This is used when
740 // determining what controls have changed given a state event or partial state update.
741 internal ControlBitRangeNode[] m_ControlTreeNodes;
742
743 // An indirection table for control bit range nodes to point at zero or more controls. Indices are used to
744 // point into the m_ChildrenForEachControl array.
745 internal ushort[] m_ControlTreeIndices;
746
747 // When a device gets built from a layout, we create a binary tree from its controls where each node in the tree
748 // represents the range of bits that cover the left or right section of the parent range. For example, starting
749 // with the entire device state block as the parent, where the state block is 100 bits long, the left node will
750 // cover from bits 0-50, and the right from bits 51-99. For the left node, we'll get two more child nodes where
751 // the left will cover bits 0-25, and the right bits 26-49 and so on. Each node will point at any controls that
752 // either fit exactly into its range, or overlap the splitting point between both nodes. In reality, picking the
753 // mid-point to split each parent node is a little convoluted and will rarely be the absolute mid-point, but that's
754 // the basic idea.
755 //
756 // At runtime, when state events come in, we can then really quickly perform a bunch of memcmps on both sides of
757 // the tree and recurse down the branches that have changed. When nodes have controls, we can then check if those
758 // controls have changes, and mark them as stale so their cached values get updated the next time their values
759 // are read.
760 [StructLayout(LayoutKind.Sequential, Pack = 1)]
761 internal struct ControlBitRangeNode
762 {
763 // only store the end bit offset of each range because we always do a full tree traversal so
764 // the start offset is always calculated at each level.
765 public ushort endBitOffset;
766
767 // points to the location in the nodes array where the left child of this node lives, or -1 if there
768 // is no child. The right child is always at the next index.
769 public short leftChildIndex;
770
771 // each node can point at multiple controls (because multiple controls can use the same range in memory and
772 // also because of overlaps in bit ranges). The control indicies for each node are stored contiguously in the
773 // m_ControlTreeIndicies array on the device, which acts as an indirection table, and these two values tell
774 // us where to start for each node and how many controls this node points at. This is an unsigned short so that
775 // we could in theory support devices with up to 65535 controls. Each node however can only support 255 controls.
776 public ushort controlStartIndex;
777 public byte controlCount;
778
779 public ControlBitRangeNode(ushort endOffset)
780 {
781 controlStartIndex = 0;
782 controlCount = 0;
783 endBitOffset = endOffset;
784 leftChildIndex = -1;
785 }
786 }
787
788 // ATM we pack everything into 32 bits. Given we're operating on bit offsets and counts, this imposes some tight limits
789 // on controls and their associated state memory. Should this turn out to be a problem, bump m_StateOffsetToControlMap
790 // to a ulong[] and up the counts here to account for having 64 bits available instead of only 32.
791 internal const int kControlIndexBits = 10; // 1024 controls max.
792 internal const int kStateOffsetBits = 13; // 1024 bytes max state size for entire device.
793 internal const int kStateSizeBits = 9; // 64 bytes max for an individual leaf control.
794
795 internal static uint EncodeStateOffsetToControlMapEntry(uint controlIndex, uint stateOffsetInBits, uint stateSizeInBits)
796 {
797 Debug.Assert(kControlIndexBits < 32, $"Expected kControlIndexBits < 32, so we fit into the 32 bit wide bitmask");
798 Debug.Assert(kStateOffsetBits < 32, $"Expected kStateOffsetBits < 32, so we fit into the 32 bit wide bitmask");
799 Debug.Assert(kStateSizeBits < 32, $"Expected kStateSizeBits < 32, so we fit into the 32 bit wide bitmask");
800 Debug.Assert(controlIndex < (1U << kControlIndexBits), "Control index beyond what is supported");
801 Debug.Assert(stateOffsetInBits < (1U << kStateOffsetBits), "State offset beyond what is supported");
802 Debug.Assert(stateSizeInBits < (1U << kStateSizeBits), "State size beyond what is supported");
803 return stateOffsetInBits << (kControlIndexBits + kStateSizeBits) | stateSizeInBits << kControlIndexBits | controlIndex;
804 }
805
806 internal static void DecodeStateOffsetToControlMapEntry(uint entry, out uint controlIndex,
807 out uint stateOffset, out uint stateSize)
808 {
809 controlIndex = entry & (1U << kControlIndexBits) - 1;
810 stateOffset = entry >> (kControlIndexBits + kStateSizeBits);
811 stateSize = (entry >> kControlIndexBits) & (((1U << (kControlIndexBits + kStateSizeBits)) - 1) >> kControlIndexBits);
812 }
813
814 // NOTE: We don't store processors in a combined array the same way we do for
815 // usages and children as that would require lots of casting from 'object'.
816
817 /// <summary>
818 /// If true, the device has at least one control that has an explicit default state.
819 /// </summary>
820 internal bool hasControlsWithDefaultState
821 {
822 get => (m_DeviceFlags & DeviceFlags.HasControlsWithDefaultState) == DeviceFlags.HasControlsWithDefaultState;
823 set
824 {
825 if (value)
826 m_DeviceFlags |= DeviceFlags.HasControlsWithDefaultState;
827 else
828 m_DeviceFlags &= ~DeviceFlags.HasControlsWithDefaultState;
829 }
830 }
831
832 internal bool hasDontResetControls
833 {
834 get => (m_DeviceFlags & DeviceFlags.HasDontResetControls) == DeviceFlags.HasDontResetControls;
835 set
836 {
837 if (value)
838 m_DeviceFlags |= DeviceFlags.HasDontResetControls;
839 else
840 m_DeviceFlags &= ~DeviceFlags.HasDontResetControls;
841 }
842 }
843
844 internal bool hasStateCallbacks
845 {
846 get => (m_DeviceFlags & DeviceFlags.HasStateCallbacks) == DeviceFlags.HasStateCallbacks;
847 set
848 {
849 if (value)
850 m_DeviceFlags |= DeviceFlags.HasStateCallbacks;
851 else
852 m_DeviceFlags &= ~DeviceFlags.HasStateCallbacks;
853 }
854 }
855
856 internal bool hasEventMerger
857 {
858 get => (m_DeviceFlags & DeviceFlags.HasEventMerger) == DeviceFlags.HasEventMerger;
859 set
860 {
861 if (value)
862 m_DeviceFlags |= DeviceFlags.HasEventMerger;
863 else
864 m_DeviceFlags &= ~DeviceFlags.HasEventMerger;
865 }
866 }
867
868 internal bool hasEventPreProcessor
869 {
870 get => (m_DeviceFlags & DeviceFlags.HasEventPreProcessor) == DeviceFlags.HasEventPreProcessor;
871 set
872 {
873 if (value)
874 m_DeviceFlags |= DeviceFlags.HasEventPreProcessor;
875 else
876 m_DeviceFlags &= ~DeviceFlags.HasEventPreProcessor;
877 }
878 }
879
880 internal void AddDeviceUsage(InternedString usage)
881 {
882 var controlUsageCount = m_UsageToControl.LengthSafe();
883 var totalUsageCount = controlUsageCount + m_UsageCount;
884 if (m_UsageCount == 0)
885 m_UsageStartIndex = totalUsageCount;
886 ArrayHelpers.AppendWithCapacity(ref m_UsagesForEachControl, ref totalUsageCount, usage);
887 ++m_UsageCount;
888 }
889
890 internal void RemoveDeviceUsage(InternedString usage)
891 {
892 var controlUsageCount = m_UsageToControl.LengthSafe();
893 var totalUsageCount = controlUsageCount + m_UsageCount;
894
895 var index = ArrayHelpers.IndexOfValue(m_UsagesForEachControl, usage, m_UsageStartIndex, totalUsageCount);
896 if (index == -1)
897 return;
898
899 Debug.Assert(m_UsageCount > 0);
900 ArrayHelpers.EraseAtWithCapacity(m_UsagesForEachControl, ref totalUsageCount, index);
901 --m_UsageCount;
902
903 if (m_UsageCount == 0)
904 m_UsageStartIndex = default;
905 }
906
907 internal void ClearDeviceUsages()
908 {
909 for (var i = m_UsageStartIndex; i < m_UsageCount; ++i)
910 m_UsagesForEachControl[i] = default;
911 m_UsageCount = default;
912 }
913
914 internal bool RequestSync()
915 {
916 SetOptimizedControlDataTypeRecursively();
917
918 var syncCommand = RequestSyncCommand.Create();
919 return device.ExecuteCommand(ref syncCommand) >= 0;
920 }
921
922 internal bool RequestReset()
923 {
924 SetOptimizedControlDataTypeRecursively();
925
926 var resetCommand = RequestResetCommand.Create();
927 return device.ExecuteCommand(ref resetCommand) >= 0;
928 }
929
930 internal bool ExecuteEnableCommand()
931 {
932 SetOptimizedControlDataTypeRecursively();
933
934 var command = EnableDeviceCommand.Create();
935 return device.ExecuteCommand(ref command) >= 0;
936 }
937
938 internal bool ExecuteDisableCommand()
939 {
940 var command = DisableDeviceCommand.Create();
941 return device.ExecuteCommand(ref command) >= 0;
942 }
943
944 internal void NotifyAdded()
945 {
946 OnAdded();
947 }
948
949 internal void NotifyRemoved()
950 {
951 OnRemoved();
952 }
953
954 internal static TDevice Build<TDevice>(string layoutName = default, string layoutVariants = default, InputDeviceDescription deviceDescription = default, bool noPrecompiledLayouts = false)
955 where TDevice : InputDevice
956 {
957 var internedLayoutName = new InternedString(layoutName);
958
959 if (internedLayoutName.IsEmpty())
960 {
961 internedLayoutName = InputControlLayout.s_Layouts.TryFindLayoutForType(typeof(TDevice));
962 if (internedLayoutName.IsEmpty())
963 internedLayoutName = new InternedString(typeof(TDevice).Name);
964 }
965
966 // Fast path: see if we can use a precompiled version.
967 // NOTE: We currently do not support layout variants with precompiled layouts.
968 // NOTE: We remove precompiled layouts when they are invalidated by layout changes. So, we don't have to perform
969 // checks here.
970 if (!noPrecompiledLayouts &&
971 string.IsNullOrEmpty(layoutVariants) &&
972 InputControlLayout.s_Layouts.precompiledLayouts.TryGetValue(internedLayoutName, out var precompiledLayout))
973 {
974 // Yes. This is pretty much a direct new() of the device.
975 return (TDevice)precompiledLayout.factoryMethod();
976 }
977
978 // Slow path: use InputDeviceBuilder to construct the device from the InputControlLayout.
979 using (InputDeviceBuilder.Ref())
980 {
981 InputDeviceBuilder.instance.Setup(internedLayoutName, new InternedString(layoutVariants),
982 deviceDescription: deviceDescription);
983 var device = InputDeviceBuilder.instance.Finish();
984 if (!(device is TDevice deviceOfType))
985 throw new ArgumentException(
986 $"Expected device of type '{typeof(TDevice).Name}' but got device of type '{device.GetType().Name}' instead",
987 "TDevice");
988
989 return deviceOfType;
990 }
991 }
992
993 internal unsafe void WriteChangedControlStates(byte* deviceStateBuffer, void* statePtr, uint stateSizeInBytes,
994 uint stateOffsetInDevice)
995 {
996 Debug.Assert(m_ControlTreeNodes != null && m_ControlTreeIndices != null);
997
998 if (m_ControlTreeNodes.Length == 0)
999 return;
1000
1001 // Reset counter for how many controls have updated
1002 m_UpdatedButtons.Clear();
1003
1004 // if we're dealing with a delta state event or just an individual control update through InputState.ChangeState
1005 // the size of the new data will not be the same size as the device state block, so use the 'partial' change state
1006 // method to update just those controls that overlap with the changed state.
1007 if (m_StateBlock.sizeInBits != stateSizeInBytes * 8)
1008 {
1009 if (m_ControlTreeNodes[0].leftChildIndex != -1)
1010 WritePartialChangedControlStatesInternal(stateSizeInBytes * 8,
1011 stateOffsetInDevice * 8, m_ControlTreeNodes[0], 0);
1012 }
1013 else
1014 {
1015 if (m_ControlTreeNodes[0].leftChildIndex != -1)
1016 WriteChangedControlStatesInternal(statePtr, deviceStateBuffer, m_ControlTreeNodes[0], 0);
1017 }
1018 }
1019
1020 private void WritePartialChangedControlStatesInternal(uint stateSizeInBits,
1021 uint stateOffsetInDeviceInBits, ControlBitRangeNode parentNode, uint startOffset)
1022 {
1023 var leftNode = m_ControlTreeNodes[parentNode.leftChildIndex];
1024 // TODO recheck
1025 if (Math.Max(stateOffsetInDeviceInBits, startOffset) <=
1026 Math.Min(stateOffsetInDeviceInBits + stateSizeInBits, leftNode.endBitOffset))
1027 {
1028 var controlEndIndex = leftNode.controlStartIndex + leftNode.controlCount;
1029 for (int i = leftNode.controlStartIndex; i < controlEndIndex; i++)
1030 {
1031 var controlIndex = m_ControlTreeIndices[i];
1032 var control = m_ChildrenForEachControl[controlIndex];
1033 control.MarkAsStale();
1034 if (control.isButton && ((ButtonControl)control).needsToCheckFramePress)
1035 m_UpdatedButtons.Add(controlIndex);
1036 }
1037
1038 if (leftNode.leftChildIndex != -1)
1039 WritePartialChangedControlStatesInternal(stateSizeInBits, stateOffsetInDeviceInBits, leftNode, startOffset);
1040 }
1041
1042 var rightNode = m_ControlTreeNodes[parentNode.leftChildIndex + 1];
1043 // TODO recheck
1044 if (Math.Max(stateOffsetInDeviceInBits, leftNode.endBitOffset) <=
1045 Math.Min(stateOffsetInDeviceInBits + stateSizeInBits, rightNode.endBitOffset))
1046 {
1047 var controlEndIndex = rightNode.controlStartIndex + rightNode.controlCount;
1048 for (int i = rightNode.controlStartIndex; i < controlEndIndex; i++)
1049 {
1050 var controlIndex = m_ControlTreeIndices[i];
1051 var control = m_ChildrenForEachControl[controlIndex];
1052 control.MarkAsStale();
1053 if (control.isButton && ((ButtonControl)control).needsToCheckFramePress)
1054 m_UpdatedButtons.Add(controlIndex);
1055 }
1056
1057 if (rightNode.leftChildIndex != -1)
1058 WritePartialChangedControlStatesInternal(stateSizeInBits, stateOffsetInDeviceInBits, rightNode, leftNode.endBitOffset);
1059 }
1060 }
1061
1062 private void DumpControlBitRangeNode(int nodeIndex, ControlBitRangeNode node, uint startOffset, uint sizeInBits, List<string> output)
1063 {
1064 var names = new List<string>();
1065 for (var i = 0; i < node.controlCount; i++)
1066 {
1067 var controlIndex = m_ControlTreeIndices[node.controlStartIndex + i];
1068 var control = m_ChildrenForEachControl[controlIndex];
1069 names.Add(control.path);
1070 }
1071 var namesStr = string.Join(", ", names);
1072 var children = node.leftChildIndex != -1 ? $" <{node.leftChildIndex}, {node.leftChildIndex + 1}>" : "";
1073 output.Add($"{nodeIndex} [{startOffset}, {startOffset + sizeInBits}]{children}->{namesStr}");
1074 }
1075
1076 private void DumpControlTree(ControlBitRangeNode parentNode, uint startOffset, List<string> output)
1077 {
1078 var leftNode = m_ControlTreeNodes[parentNode.leftChildIndex];
1079 var rightNode = m_ControlTreeNodes[parentNode.leftChildIndex + 1];
1080 DumpControlBitRangeNode(parentNode.leftChildIndex, leftNode, startOffset, leftNode.endBitOffset - startOffset, output);
1081 DumpControlBitRangeNode(parentNode.leftChildIndex + 1, rightNode, leftNode.endBitOffset, (uint)(rightNode.endBitOffset - leftNode.endBitOffset), output);
1082
1083 if (leftNode.leftChildIndex != -1)
1084 DumpControlTree(leftNode, startOffset, output);
1085
1086 if (rightNode.leftChildIndex != -1)
1087 DumpControlTree(rightNode, leftNode.endBitOffset, output);
1088 }
1089
1090 internal string DumpControlTree()
1091 {
1092 var output = new List<string>();
1093 DumpControlTree(m_ControlTreeNodes[0], 0, output);
1094 return string.Join("\n", output);
1095 }
1096
1097 private unsafe void WriteChangedControlStatesInternal(void* statePtr,
1098 byte* deviceStatePtr, ControlBitRangeNode parentNode, uint startOffset)
1099 {
1100 var leftNode = m_ControlTreeNodes[parentNode.leftChildIndex];
1101
1102 // have any bits in the region defined by the left node changed?
1103 // TODO recheck
1104 if (HasDataChangedInRange(deviceStatePtr, statePtr, startOffset, leftNode.endBitOffset - startOffset + 1))
1105 {
1106 // update the state of any controls pointed to by the left node
1107 var controlEndIndex = leftNode.controlStartIndex + leftNode.controlCount;
1108 for (int i = leftNode.controlStartIndex; i < controlEndIndex; i++)
1109 {
1110 var controlIndex = m_ControlTreeIndices[i];
1111 var control = m_ChildrenForEachControl[controlIndex];
1112
1113 // nodes aren't always an exact fit for control memory ranges so check here if the control pointed
1114 // at by this node has actually changed state so we don't mark controls as stale needlessly.
1115 // We need to offset the device and new state pointers by the byte offset of the device state block
1116 // because all controls have this offset baked into them, but deviceStatePtr points at the already
1117 // offset block of device memory (remember, all devices share one big block of memory) and statePtr
1118 // points at a block of memory of the same size as the device state.
1119 if (!control.CompareState(deviceStatePtr - m_StateBlock.byteOffset,
1120 (byte*)statePtr - m_StateBlock.byteOffset, null))
1121 {
1122 control.MarkAsStale();
1123 if (control.isButton && ((ButtonControl)control).needsToCheckFramePress)
1124 m_UpdatedButtons.Add(controlIndex);
1125 }
1126 }
1127
1128 // process the left child node if it exists
1129 if (leftNode.leftChildIndex != -1)
1130 WriteChangedControlStatesInternal(statePtr, deviceStatePtr,
1131 leftNode, startOffset);
1132 }
1133
1134 // process the right child node if it exists
1135 var rightNode = m_ControlTreeNodes[parentNode.leftChildIndex + 1];
1136
1137 Debug.Assert(leftNode.endBitOffset + (rightNode.endBitOffset - leftNode.endBitOffset) < m_StateBlock.sizeInBits,
1138 "Tried to check state memory outside the bounds of the current device.");
1139
1140 // if no bits in the range defined by the right node have changed, return
1141 // TODO recheck
1142 if (!HasDataChangedInRange(deviceStatePtr, statePtr, leftNode.endBitOffset,
1143 (uint)(rightNode.endBitOffset - leftNode.endBitOffset + 1)))
1144 return;
1145
1146 // update the state of any controls pointed to by the right node
1147 var rightNodeControlEndIndex = rightNode.controlStartIndex + rightNode.controlCount;
1148 for (int i = rightNode.controlStartIndex; i < rightNodeControlEndIndex; i++)
1149 {
1150 var controlIndex = m_ControlTreeIndices[i];
1151 var control = m_ChildrenForEachControl[controlIndex];
1152
1153 if (!control.CompareState(deviceStatePtr - m_StateBlock.byteOffset,
1154 (byte*)statePtr - m_StateBlock.byteOffset, null))
1155 {
1156 control.MarkAsStale();
1157 if (control.isButton && ((ButtonControl)control).needsToCheckFramePress)
1158 m_UpdatedButtons.Add(controlIndex);
1159 }
1160 }
1161
1162 if (rightNode.leftChildIndex != -1)
1163 WriteChangedControlStatesInternal(statePtr, deviceStatePtr,
1164 rightNode, leftNode.endBitOffset);
1165 }
1166
1167 private static unsafe bool HasDataChangedInRange(byte* deviceStatePtr, void* statePtr, uint startOffset, uint sizeInBits)
1168 {
1169 if (sizeInBits == 1)
1170 return MemoryHelpers.ReadSingleBit(deviceStatePtr, startOffset) !=
1171 MemoryHelpers.ReadSingleBit(statePtr, startOffset);
1172
1173 return !MemoryHelpers.MemCmpBitRegion(deviceStatePtr, statePtr,
1174 startOffset, sizeInBits);
1175 }
1176 }
1177}