 9092858a58
			
		
	
	
		9092858a58
		
	
	
	
	
		
			
			I updated everything to the latest Unity Editor. Also realized I had the wrong shaders on my hairs, those are fixed and the hairs look MUCH better!
		
			
				
	
	
		
			1138 lines
		
	
	
		
			56 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			1138 lines
		
	
	
		
			56 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.Runtime.InteropServices;
 | |
| using UnityEngine.InputSystem.LowLevel;
 | |
| using UnityEngine.InputSystem.Utilities;
 | |
| using Unity.Collections.LowLevel.Unsafe;
 | |
| using UnityEngine.InputSystem.Layouts;
 | |
| 
 | |
| ////TODO: runtime remapping of control usages on a per-device basis
 | |
| 
 | |
| ////TODO: finer-grained control over what devices deliver input while running in background
 | |
| ////      (e.g. get gamepad input but do *not* get mouse and keyboard input)
 | |
| 
 | |
| ////REVIEW: should be possible to completely hijack the input stream of a device such that its original input is suppressed
 | |
| 
 | |
| ////REVIEW: can we construct the control tree of devices on demand so that the user never has to pay for
 | |
| ////        the heap objects of devices that aren't used?
 | |
| 
 | |
| // per device functions:
 | |
| //  - update/poll
 | |
| //  - IOCTL
 | |
| //  - text input
 | |
| //  - configuration change
 | |
| //  - make current
 | |
| //  - on remove (also resets current)
 | |
| //
 | |
| // Ideally, these would *not* be virtual methods on InputDevice but use a different process (which?)
 | |
| // for associating responses with devices
 | |
| 
 | |
| namespace UnityEngine.InputSystem
 | |
| {
 | |
|     /// <summary>
 | |
|     /// Represents an input device which is always the root of a hierarchy of <see cref="InputControl"/> instances.
 | |
|     /// </summary>
 | |
|     /// <remarks>
 | |
|     /// Input devices act as the container for control hierarchies. Every hierarchy has to have
 | |
|     /// a device at the root. Devices cannot occur as children of other controls.
 | |
|     ///
 | |
|     /// Devices are usually created automatically in response to hardware being discovered by the Unity
 | |
|     /// runtime. However, it is possible to manually add devices using methods such as <see
 | |
|     /// cref="InputSystem.AddDevice{TDevice}(string)"/>.
 | |
|     ///
 | |
|     /// <example>
 | |
|     /// <code>
 | |
|     /// // Add a "synthetic" gamepad that isn't actually backed by hardware.
 | |
|     /// var gamepad = InputSystem.AddDevice<Gamepad>();
 | |
|     /// </code>
 | |
|     /// </example>
 | |
|     ///
 | |
|     /// There are subclasses representing the most common types of devices, like <see cref="Mouse"/>,
 | |
|     /// <see cref="Keyboard"/>, <see cref="Gamepad"/>, and <see cref="Touchscreen"/>.
 | |
|     ///
 | |
|     /// To create your own types of devices, you can derive from InputDevice and register your device
 | |
|     /// as a new "layout".
 | |
|     ///
 | |
|     /// <example>
 | |
|     /// <code>
 | |
|     /// // InputControlLayoutAttribute attribute is only necessary if you want
 | |
|     /// // to override default behavior that occurs when registering your device
 | |
|     /// // as a layout.
 | |
|     /// // The most common use of InputControlLayoutAttribute is to direct the system
 | |
|     /// // to a custom "state struct" through the `stateType` property. See below for details.
 | |
|     /// [InputControlLayout(displayName = "My Device", stateType = typeof(MyDeviceState))]
 | |
|     /// #if UNITY_EDITOR
 | |
|     /// [InitializeOnLoad]
 | |
|     /// #endif
 | |
|     /// public class MyDevice : InputDevice
 | |
|     /// {
 | |
|     ///     public ButtonControl button { get; private set; }
 | |
|     ///     public AxisControl axis { get; private set; }
 | |
|     ///
 | |
|     ///     // Register the device.
 | |
|     ///     static MyDevice()
 | |
|     ///     {
 | |
|     ///         // In case you want instance of your device to automatically be created
 | |
|     ///         // when specific hardware is detected by the Unity runtime, you have to
 | |
|     ///         // add one or more "device matchers" (InputDeviceMatcher) for the layout.
 | |
|     ///         // These matchers are compared to an InputDeviceDescription received from
 | |
|     ///         // the Unity runtime when a device is connected. You can add them either
 | |
|     ///         // using InputSystem.RegisterLayoutMatcher() or by directly specifying a
 | |
|     ///         // matcher when registering the layout.
 | |
|     ///         InputSystem.RegisterLayout<MyDevice>(
 | |
|     ///             // For the sake of demonstration, let's assume your device is a HID
 | |
|     ///             // and you want to match by PID and VID.
 | |
|     ///             matches: new InputDeviceMatcher()
 | |
|     ///                 .WithInterface("HID")
 | |
|     ///                 .WithCapability("PID", 1234)
 | |
|     ///                 .WithCapability("VID", 5678));
 | |
|     ///     }
 | |
|     ///
 | |
|     ///     // This is only to trigger the static class constructor to automatically run
 | |
|     ///     // in the player.
 | |
|     ///     [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
 | |
|     ///     private static void InitializeInPlayer() {}
 | |
|     ///
 | |
|     ///     protected override void FinishSetup()
 | |
|     ///     {
 | |
|     ///         base.FinishSetup();
 | |
|     ///         button = GetChildControl<ButtonControl>("button");
 | |
|     ///         axis = GetChildControl<AxisControl>("axis");
 | |
|     ///     }
 | |
|     /// }
 | |
|     ///
 | |
|     /// // A "state struct" describes the memory format used by a device. Each device can
 | |
|     /// // receive and store memory in its custom format. InputControls are then connected
 | |
|     /// // the individual pieces of memory and read out values from them.
 | |
|     /// [StructLayout(LayoutKind.Explicit, Size = 32)]
 | |
|     /// public struct MyDeviceState : IInputStateTypeInfo
 | |
|     /// {
 | |
|     ///     // In the case of a HID (which we assume for the sake of this demonstration),
 | |
|     ///     // the format will be "HID". In practice, the format will depend on how your
 | |
|     ///     // particular device is connected and fed into the input system.
 | |
|     ///     // The format is a simple FourCC code that "tags" state memory blocks for the
 | |
|     ///     // device to give a base level of safety checks on memory operations.
 | |
|     ///     public FourCC format => return new FourCC('H', 'I', 'D');
 | |
|     ///
 | |
|     ///     // InputControlAttributes on fields tell the input system to create controls
 | |
|     ///     // for the public fields found in the struct.
 | |
|     ///
 | |
|     ///     // Assume a 16bit field of buttons. Create one button that is tied to
 | |
|     ///     // bit #3 (zero-based). Note that buttons do not need to be stored as bits.
 | |
|     ///     // They can also be stored as floats or shorts, for example.
 | |
|     ///     [InputControl(name = "button", layout = "Button", bit = 3)]
 | |
|     ///     public ushort buttons;
 | |
|     ///
 | |
|     ///     // Create a floating-point axis. The name, if not supplied, is taken from
 | |
|     ///     // the field.
 | |
|     ///     [InputControl(layout = "Axis")]
 | |
|     ///     public short axis;
 | |
|     /// }
 | |
|     /// </code>
 | |
|     /// </example>
 | |
|     ///
 | |
|     /// Devices can have usages like any other control (<see cref="InputControl.usages"/>). Unlike other controls,
 | |
|     /// however, usages of InputDevices are allowed to be changed on the fly without requiring a change to the
 | |
|     /// device layout (see <see cref="InputSystem.SetDeviceUsage(InputDevice,string)"/>).
 | |
|     ///
 | |
|     /// For a more complete example of how to implement custom input devices, check out the "Custom Device"
 | |
|     /// sample which you can install from the Unity package manager.
 | |
|     ///
 | |
|     /// And, as always, you can also find more information in the <a href="../manual/Devices.html">manual</a>.
 | |
|     /// </remarks>
 | |
|     /// <seealso cref="InputControl"/>
 | |
|     /// <seealso cref="Mouse"/>
 | |
|     /// <seealso cref="Keyboard"/>
 | |
|     /// <seealso cref="Gamepad"/>
 | |
|     /// <seealso cref="Touchscreen"/>
 | |
|     public class InputDevice : InputControl
 | |
|     {
 | |
|         /// <summary>
 | |
|         /// Value of an invalid <see cref="deviceId"/>.
 | |
|         /// </summary>
 | |
|         /// <remarks>
 | |
|         /// The input system will not assigned this ID to any device.
 | |
|         /// </remarks>
 | |
|         public const int InvalidDeviceId = 0;
 | |
| 
 | |
|         internal const int kLocalParticipantId = 0;
 | |
|         internal const int kInvalidDeviceIndex = -1;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Metadata describing the device (product name etc.).
 | |
|         /// </summary>
 | |
|         /// <remarks>
 | |
|         /// The description of a device is unchanging over its lifetime and does not
 | |
|         /// comprise data about a device's configuration (which is considered mutable).
 | |
|         ///
 | |
|         /// In most cases, the description for a device is supplied by the Unity runtime.
 | |
|         /// This it the case for all <see cref="native"/> input devices. However, it is
 | |
|         /// also possible to inject new devices in the form of device descriptions into
 | |
|         /// the system using <see cref="InputSystem.AddDevice(InputDeviceDescription)"/>.
 | |
|         ///
 | |
|         /// The description of a device is what is matched by an <see cref="InputDeviceMatcher"/>
 | |
|         /// to find the <see cref="InputControl.layout"/> to use for a device.
 | |
|         /// </remarks>
 | |
|         public InputDeviceDescription description => m_Description;
 | |
| 
 | |
|         ////REVIEW: When we can break the API, probably makes sense to replace this single bool with one for sending and one for receiving events
 | |
|         /// <summary>
 | |
|         /// Whether the device is currently enabled (that is, sends and receives events).
 | |
|         /// </summary>
 | |
|         /// <remarks>
 | |
|         /// A device that is disabled will not receive events. I.e. events that are being sent to the device
 | |
|         /// will be ignored.
 | |
|         ///
 | |
|         /// When disabling a <see cref="native"/> device, a <see cref="DisableDeviceCommand">disable command</see> will
 | |
|         /// also be sent to the <see cref="IInputRuntime">runtime</see>. It depends on the specific runtime whether the
 | |
|         /// device command is supported but if it is, the device will be disabled in the runtime and no longer send
 | |
|         /// events. This is especially important for devices such as <see cref="Sensor">sensors</see> that incur both
 | |
|         /// computation and battery consumption overhead while enabled.
 | |
|         ///
 | |
|         /// Specific types of devices can choose to start out in disabled state by default. This is generally the
 | |
|         /// case for <see cref="Sensor">sensors</see> to ensure that their overhead is only incurred when actually
 | |
|         /// being used by the application.
 | |
|         /// </remarks>
 | |
|         /// <seealso cref="InputSystem.EnableDevice"/>
 | |
|         /// <seealso cref="InputSystem.DisableDevice"/>
 | |
|         public bool enabled
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 #if UNITY_EDITOR
 | |
|                 if (InputState.currentUpdateType == InputUpdateType.Editor && (m_DeviceFlags & DeviceFlags.DisabledWhileInBackground) != 0)
 | |
|                     return true;
 | |
|                 #endif
 | |
| 
 | |
|                 if ((m_DeviceFlags & (DeviceFlags.DisabledInFrontend | DeviceFlags.DisabledWhileInBackground)) != 0)
 | |
|                     return false;
 | |
| 
 | |
|                 return QueryEnabledStateFromRuntime();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         ////TODO: rename this to canReceiveInputInBackground (once we can break API)
 | |
|         /// <summary>
 | |
|         /// If true, the device is capable of delivering input while the application is running in the background, i.e.
 | |
|         /// while <c>Application.isFocused</c> is false.
 | |
|         /// </summary>
 | |
|         /// <value>Whether the device can generate input while in the background.</value>
 | |
|         /// <remarks>
 | |
|         /// The value of this property is determined by three separator factors.
 | |
|         ///
 | |
|         /// For one, <see cref="native"/> devices have an inherent value for this property that can be retrieved through
 | |
|         /// <see cref="QueryCanRunInBackground"/>. This determines whether at the input collection level, the device is
 | |
|         /// capable of producing input independent of application. This is rare and only a select set of hardware, platform,
 | |
|         /// and SDK/API combinations support this. The prominent class of input devices that in general do support this
 | |
|         /// behavior are VR devices.
 | |
|         ///
 | |
|         /// Furthermore, the property may be force-set through a device's <see cref="InputControl.layout"/> by
 | |
|         /// means of <see cref="InputControlLayout.canRunInBackground"/>.
 | |
|         ///
 | |
|         /// Lastly, in the editor, the value of the property may be overridden depending on <see cref="InputSettings.editorInputBehaviorInPlayMode"/>
 | |
|         /// in case certain devices are automatically kept running in play mode even when no Game View has focus.
 | |
|         ///
 | |
|         /// Be aware that as far as players are concerned, only certain platforms support running Unity while not having focus.
 | |
|         /// On mobile platforms, for example, this is generally not supported. In this case, the value of this property
 | |
|         /// has no impact on input while the application does not have focus. See <see cref="InputSettings.backgroundBehavior"/>
 | |
|         /// for more details.
 | |
|         /// </remarks>
 | |
|         /// <seealso cref="InputSettings.backgroundBehavior"/>
 | |
|         /// <seealso cref="InputControlLayout.canRunInBackground"/>
 | |
|         public bool canRunInBackground
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 // In the editor, "background" refers to "game view not focused", not to the editor not being active.
 | |
|                 // So, we modulate canRunInBackground depending on how input should behave WRT game view according
 | |
|                 // to the input settings.
 | |
|                 #if UNITY_EDITOR
 | |
|                 var gameViewFocus = InputSystem.settings.editorInputBehaviorInPlayMode;
 | |
|                 if (gameViewFocus == InputSettings.EditorInputBehaviorInPlayMode.AllDevicesRespectGameViewFocus)
 | |
|                     return false; // No device considered being able to run without game view focus.
 | |
|                 if (gameViewFocus == InputSettings.EditorInputBehaviorInPlayMode.PointersAndKeyboardsRespectGameViewFocus)
 | |
|                     return !(this is Pointer || this is Keyboard); // Anything but pointers and keyboards considered as being able to run in background.
 | |
|                 #endif
 | |
| 
 | |
|                 if ((m_DeviceFlags & DeviceFlags.CanRunInBackgroundHasBeenQueried) != 0)
 | |
|                     return (m_DeviceFlags & DeviceFlags.CanRunInBackground) != 0;
 | |
| 
 | |
|                 var command = QueryCanRunInBackground.Create();
 | |
|                 m_DeviceFlags |= DeviceFlags.CanRunInBackgroundHasBeenQueried;
 | |
|                 if (ExecuteCommand(ref command) >= 0 && command.canRunInBackground)
 | |
|                 {
 | |
|                     m_DeviceFlags |= DeviceFlags.CanRunInBackground;
 | |
|                     return true;
 | |
|                 }
 | |
| 
 | |
|                 m_DeviceFlags &= ~DeviceFlags.CanRunInBackground;
 | |
|                 return false;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Whether the device has been added to the system.
 | |
|         /// </summary>
 | |
|         /// <value>If true, the device is currently among the devices in <see cref="InputSystem.devices"/>.</value>
 | |
|         /// <remarks>
 | |
|         /// Devices may be removed at any time. Either when their hardware is unplugged or when they
 | |
|         /// are manually removed through <see cref="InputSystem.RemoveDevice"/> or by being excluded
 | |
|         /// through <see cref="InputSettings.supportedDevices"/>. When a device is removed, its instance,
 | |
|         /// however, will not disappear. This property can be used to check whether the device is part
 | |
|         /// of the current set of active devices.
 | |
|         /// </remarks>
 | |
|         /// <seealso cref="InputSystem.devices"/>
 | |
|         public bool added => m_DeviceIndex != kInvalidDeviceIndex;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Whether the device is mirrored from a remote input system and not actually present
 | |
|         /// as a "real" device in the local system.
 | |
|         /// </summary>
 | |
|         /// <value>Whether the device mirrors a device from a remotely connected input system.</value>
 | |
|         /// <seealso cref="InputSystem.remoting"/>
 | |
|         /// <seealso cref="InputRemoting"/>
 | |
|         public bool remote => (m_DeviceFlags & DeviceFlags.Remote) == DeviceFlags.Remote;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Whether the device comes from the <see cref="IInputRuntime">runtime</see>
 | |
|         /// </summary>
 | |
|         /// <value>Whether the device has been discovered by the Unity runtime.</value>
 | |
|         /// <remarks>
 | |
|         /// Devices can be discovered when <see cref="IInputRuntime.onDeviceDiscovered">reported</see>
 | |
|         /// by the runtime or they can be added manually through the various <see cref="InputSystem.AddDevice(InputDevice)">
 | |
|         /// AddDevice</see> APIs. Devices reported by the runtime will return true for this
 | |
|         /// property whereas devices added manually will return false.
 | |
|         ///
 | |
|         /// Devices reported by the runtime will usually come from the Unity engine itself.
 | |
|         /// </remarks>
 | |
|         /// <seealso cref="IInputRuntime"/>
 | |
|         /// <seealso cref="IInputRuntime.onDeviceDiscovered"/>
 | |
|         public bool native => (m_DeviceFlags & DeviceFlags.Native) == DeviceFlags.Native;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Whether the device requires an extra update before rendering.
 | |
|         /// </summary>
 | |
|         /// <remarks>
 | |
|         /// The value of this property is determined by <see cref="InputControlLayout.updateBeforeRender"/> in
 | |
|         /// the device's <see cref="InputControlLayout">control layout</see>.
 | |
|         ///
 | |
|         /// The extra update is necessary for tracking devices that are used in rendering code. For example,
 | |
|         /// the eye transforms of an HMD should be refreshed right before rendering as refreshing only in the
 | |
|         /// beginning of the frame will lead to a noticeable lag.
 | |
|         /// </remarks>
 | |
|         /// <seealso cref="InputUpdateType.BeforeRender"/>
 | |
|         public bool updateBeforeRender => (m_DeviceFlags & DeviceFlags.UpdateBeforeRender) == DeviceFlags.UpdateBeforeRender;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Unique numeric ID for the device.
 | |
|         /// </summary>
 | |
|         /// <remarks>
 | |
|         /// This is only assigned once a device has been added to the system. No two devices will receive the same
 | |
|         /// ID and no device will receive an ID that another device used before even if the device was removed. The
 | |
|         /// only exception to this is if a device gets re-created as part of a layout change. For example, if a new
 | |
|         /// layout is registered that replaces the <see cref="Mouse"/> layout, all <see cref="Mouse"/> devices will
 | |
|         /// get recreated but will keep their existing device IDs.
 | |
|         ///
 | |
|         /// IDs are assigned by the input runtime.
 | |
|         /// </remarks>
 | |
|         /// <seealso cref="IInputRuntime.AllocateDeviceId"/>
 | |
|         public int deviceId => m_DeviceId;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Timestamp of last state event used to update the device.
 | |
|         /// </summary>
 | |
|         /// <remarks>
 | |
|         /// Events other than <see cref="LowLevel.StateEvent"/> and <see cref="LowLevel.DeltaStateEvent"/> will
 | |
|         /// not cause lastUpdateTime to be changed.
 | |
|         /// The "timeline" is reset to 0 when entering play mode. If there are any events incoming or device
 | |
|         /// updates which occur prior to entering play mode, these will appear negative.
 | |
|         /// </remarks>
 | |
|         public double lastUpdateTime => m_LastUpdateTimeInternal - InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup;
 | |
| 
 | |
|         public bool wasUpdatedThisFrame => m_CurrentUpdateStepCount == InputUpdate.s_UpdateStepCount;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// A flattened list of controls that make up the device.
 | |
|         /// </summary>
 | |
|         /// <remarks>
 | |
|         /// Does not allocate.
 | |
|         /// </remarks>
 | |
|         public ReadOnlyArray<InputControl> allControls =>
 | |
|             // Since m_ChildrenForEachControl contains the device's children as well as the children
 | |
|             // of each control in the hierarchy, and since each control can only have a single parent,
 | |
|             // this list will actually deliver a flattened list of all controls in the hierarchy (and without
 | |
|             // the device itself being listed).
 | |
|             new ReadOnlyArray<InputControl>(m_ChildrenForEachControl);
 | |
| 
 | |
|         ////REVIEW: This violates the constraint of controls being required to not have reference types as value types.
 | |
|         /// <inheritdoc/>
 | |
|         public override Type valueType => typeof(byte[]);
 | |
| 
 | |
|         /// <inheritdoc/>
 | |
|         public override int valueSizeInBytes => (int)m_StateBlock.alignedSizeInBytes;
 | |
| 
 | |
|         // This one just leads to confusion as you can access it from subclasses and then be surprised
 | |
|         // that it doesn't only include members of those classes.
 | |
|         [Obsolete("Use 'InputSystem.devices' instead. (UnityUpgradable) -> InputSystem.devices", error: false)]
 | |
|         public static ReadOnlyArray<InputDevice> all => InputSystem.devices;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// This constructor is public for the sake of <c>Activator.CreateInstance</c> only. To construct
 | |
|         /// devices, use methods such as <see cref="InputSystem.AddDevice{TDevice}(string)"/>. Manually
 | |
|         /// using <c>new</c> on InputDevice will not result in a usable device.
 | |
|         /// </summary>
 | |
|         public InputDevice()
 | |
|         {
 | |
|             m_DeviceId = InvalidDeviceId;
 | |
|             m_ParticipantId = kLocalParticipantId;
 | |
|             m_DeviceIndex = kInvalidDeviceIndex;
 | |
|         }
 | |
| 
 | |
|         ////REVIEW: Is making devices be byte[] values really all that useful? Seems better than returning nulls but
 | |
|         ////        at the same time, seems questionable.
 | |
| 
 | |
|         /// <inheritdoc/>
 | |
|         public override unsafe object ReadValueFromBufferAsObject(void* buffer, int bufferSize)
 | |
|         {
 | |
|             throw new NotImplementedException();
 | |
|         }
 | |
| 
 | |
|         /// <inheritdoc/>
 | |
|         public override unsafe object ReadValueFromStateAsObject(void* statePtr)
 | |
|         {
 | |
|             if (m_DeviceIndex == kInvalidDeviceIndex)
 | |
|                 return null;
 | |
| 
 | |
|             var numBytes = stateBlock.alignedSizeInBytes;
 | |
|             var array = new byte[numBytes];
 | |
|             fixed(byte* arrayPtr = array)
 | |
|             {
 | |
|                 var adjustedStatePtr = (byte*)statePtr + m_StateBlock.byteOffset;
 | |
|                 UnsafeUtility.MemCpy(arrayPtr, adjustedStatePtr, numBytes);
 | |
|             }
 | |
| 
 | |
|             return array;
 | |
|         }
 | |
| 
 | |
|         /// <inheritdoc/>
 | |
|         public override unsafe void ReadValueFromStateIntoBuffer(void* statePtr, void* bufferPtr, int bufferSize)
 | |
|         {
 | |
|             if (statePtr == null)
 | |
|                 throw new ArgumentNullException(nameof(statePtr));
 | |
|             if (bufferPtr == null)
 | |
|                 throw new ArgumentNullException(nameof(bufferPtr));
 | |
|             if (bufferSize < valueSizeInBytes)
 | |
|                 throw new ArgumentException($"Buffer too small (expected: {valueSizeInBytes}, actual: {bufferSize}");
 | |
| 
 | |
|             var adjustedStatePtr = (byte*)statePtr + m_StateBlock.byteOffset;
 | |
|             UnsafeUtility.MemCpy(bufferPtr, adjustedStatePtr, m_StateBlock.alignedSizeInBytes);
 | |
|         }
 | |
| 
 | |
|         /// <inheritdoc/>
 | |
|         public override unsafe bool CompareValue(void* firstStatePtr, void* secondStatePtr)
 | |
|         {
 | |
|             if (firstStatePtr == null)
 | |
|                 throw new ArgumentNullException(nameof(firstStatePtr));
 | |
|             if (secondStatePtr == null)
 | |
|                 throw new ArgumentNullException(nameof(secondStatePtr));
 | |
| 
 | |
|             var adjustedFirstStatePtr = (byte*)firstStatePtr + m_StateBlock.byteOffset;
 | |
|             var adjustedSecondStatePtr = (byte*)firstStatePtr + m_StateBlock.byteOffset;
 | |
| 
 | |
|             return UnsafeUtility.MemCmp(adjustedFirstStatePtr, adjustedSecondStatePtr,
 | |
|                 m_StateBlock.alignedSizeInBytes) == 0;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called by the system when the configuration of the device has changed.
 | |
|         /// </summary>
 | |
|         /// <seealso cref="DeviceConfigurationEvent"/>
 | |
|         internal void NotifyConfigurationChanged()
 | |
|         {
 | |
|             // Mark all controls in the hierarchy as having their config out of date.
 | |
|             // We don't want to update configuration right away but rather wait until
 | |
|             // someone actually depends on it.
 | |
|             isConfigUpToDate = false;
 | |
|             for (var i = 0; i < m_ChildrenForEachControl.Length; ++i)
 | |
|                 m_ChildrenForEachControl[i].isConfigUpToDate = false;
 | |
| 
 | |
|             // Make sure we fetch the enabled/disabled state again.
 | |
|             m_DeviceFlags &= ~DeviceFlags.DisabledStateHasBeenQueriedFromRuntime;
 | |
| 
 | |
|             OnConfigurationChanged();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Make this the current device of its type.
 | |
|         /// </summary>
 | |
|         /// <remarks>
 | |
|         /// This method is called automatically by the input system when a device is
 | |
|         /// added or when input is received on it. Many types of devices have <c>.current</c>
 | |
|         /// getters that allow querying the last used device of a specific type directly (for
 | |
|         /// example, see <see cref="Gamepad.current"/>).
 | |
|         ///
 | |
|         /// There is one special case, however, related to noise. A device that has noisy controls
 | |
|         /// (i.e. controls for which <see cref="InputControl.noisy"/> is true) may receive input events
 | |
|         /// that contain no meaningful user interaction but are simply just noise from the device. A
 | |
|         /// good example of this is the PS4 gamepad which has a built-in gyro and may thus constantly
 | |
|         /// feed events into the input system even if not being actually in use. If, for example, an
 | |
|         /// Xbox gamepad and PS4 gamepad are both connected to a PC and the user is playing with the
 | |
|         /// Xbox gamepad, the PS4 gamepad would still constantly make itself <see cref="Gamepad.current"/>
 | |
|         /// by simply flooding the system with events. Hence why by default,  noise on <c>.current</c> getters
 | |
|         /// will be filtered out and a device will only see <c>MakeCurrent</c> getting called if there input
 | |
|         /// was detected on non-noisy controls.
 | |
|         /// </remarks>
 | |
|         /// <seealso cref="Pointer.current"/>
 | |
|         /// <seealso cref="Gamepad.current"/>
 | |
|         /// <seealso cref="Mouse.current"/>
 | |
|         /// <seealso cref="Pen.current"/>
 | |
|         public virtual void MakeCurrent()
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called by the system when the device is added to <see cref="InputSystem.devices"/>.
 | |
|         /// </summary>
 | |
|         /// <remarks>
 | |
|         /// This is called <em>after</em> the device has already been added.
 | |
|         /// </remarks>
 | |
|         /// <seealso cref="InputSystem.devices"/>
 | |
|         /// <seealso cref="InputDeviceChange.Added"/>
 | |
|         /// <seealso cref="OnRemoved"/>
 | |
|         protected virtual void OnAdded()
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called by the system when the device is removed from <see cref="InputSystem.devices"/>.
 | |
|         /// </summary>
 | |
|         /// <remarks>
 | |
|         /// This is called <em>after</em> the device has already been removed.
 | |
|         /// </remarks>
 | |
|         /// <seealso cref="InputSystem.devices"/>
 | |
|         /// <seealso cref="InputDeviceChange.Removed"/>
 | |
|         /// <seealso cref="OnRemoved"/>
 | |
|         protected virtual void OnRemoved()
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called by the system when the device configuration is changed. This happens when the backend sends
 | |
|         /// a <see cref="DeviceConfigurationEvent"/> for the device.
 | |
|         /// </summary>
 | |
|         /// <remarks>
 | |
|         /// This method can be used to flush out cached information. An example of where this happens is <see cref="Controls.KeyControl"/>
 | |
|         /// caching information about the display name of a control. As this depends on the current keyboard layout, the information
 | |
|         /// has to be fetched dynamically (this happens using <see cref="QueryKeyNameCommand"/>). Whenever the keyboard layout changes,
 | |
|         /// the system sends a <see cref="DeviceConfigurationEvent"/> for the <see cref="Keyboard"/> at which point the device flushes
 | |
|         /// all cached key names.
 | |
|         /// </remarks>
 | |
|         /// <seealso cref="InputManager.OnUpdate"/>
 | |
|         /// <seealso cref="InputDeviceChange.ConfigurationChanged"/>
 | |
|         /// <seealso cref="OnConfigurationChanged"/>///
 | |
|         protected virtual void OnConfigurationChanged()
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         ////TODO: add overridable OnDisable/OnEnable that fire the device commands
 | |
| 
 | |
|         ////REVIEW: return just bool instead of long and require everything else to go in the command?
 | |
|         /// <summary>
 | |
|         /// Perform a device-specific command.
 | |
|         /// </summary>
 | |
|         /// <param name="command">Data for the command to be performed.</param>
 | |
|         /// <returns>A transfer-specific return code. Negative values are considered failure codes.</returns>
 | |
|         /// <remarks>
 | |
|         /// Commands allow devices to set up custom protocols without having to extend
 | |
|         /// the device API. This is most useful for devices implemented in the native Unity runtime
 | |
|         /// which, through the command interface, may provide custom, device-specific functions.
 | |
|         ///
 | |
|         /// 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">
 | |
|         /// 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>
 | |
|         /// on UNIX-like systems.
 | |
|         /// </remarks>
 | |
|         public unsafe long ExecuteCommand<TCommand>(ref TCommand command)
 | |
|             where TCommand : struct, IInputDeviceCommandInfo
 | |
|         {
 | |
|             var commandPtr = (InputDeviceCommand*)UnsafeUtility.AddressOf(ref command);
 | |
| 
 | |
|             // Give callbacks first shot.
 | |
|             var manager = InputSystem.s_Manager;
 | |
|             manager.m_DeviceCommandCallbacks.LockForChanges();
 | |
|             for (var i = 0; i < manager.m_DeviceCommandCallbacks.length; ++i)
 | |
|             {
 | |
|                 try
 | |
|                 {
 | |
|                     var result = manager.m_DeviceCommandCallbacks[i](this, commandPtr);
 | |
|                     if (result.HasValue)
 | |
|                         return result.Value;
 | |
|                 }
 | |
|                 catch (Exception exception)
 | |
|                 {
 | |
|                     Debug.LogError($"{exception.GetType().Name} while executing 'InputSystem.onDeviceCommand' callbacks");
 | |
|                     Debug.LogException(exception);
 | |
|                 }
 | |
|             }
 | |
|             manager.m_DeviceCommandCallbacks.UnlockForChanges();
 | |
| 
 | |
|             return ExecuteCommand((InputDeviceCommand*)UnsafeUtility.AddressOf(ref command));
 | |
|         }
 | |
| 
 | |
|         protected virtual unsafe long ExecuteCommand(InputDeviceCommand* commandPtr)
 | |
|         {
 | |
|             return InputRuntime.s_Instance.DeviceCommand(deviceId, commandPtr);
 | |
|         }
 | |
| 
 | |
|         internal bool QueryEnabledStateFromRuntime()
 | |
|         {
 | |
|             // Fetch state from runtime, if necessary.
 | |
|             if ((m_DeviceFlags & DeviceFlags.DisabledStateHasBeenQueriedFromRuntime) == 0)
 | |
|             {
 | |
|                 var command = QueryEnabledStateCommand.Create();
 | |
|                 if (ExecuteCommand(ref command) >= 0)
 | |
|                 {
 | |
|                     if (command.isEnabled)
 | |
|                         m_DeviceFlags &= ~DeviceFlags.DisabledInRuntime;
 | |
|                     else
 | |
|                         m_DeviceFlags |= DeviceFlags.DisabledInRuntime;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     // We got no response on the enable/disable state. Assume device is enabled.
 | |
|                     m_DeviceFlags &= ~DeviceFlags.DisabledInRuntime;
 | |
|                 }
 | |
| 
 | |
|                 // Only fetch enable/disable state again if we get a configuration change event.
 | |
|                 m_DeviceFlags |= DeviceFlags.DisabledStateHasBeenQueriedFromRuntime;
 | |
|             }
 | |
| 
 | |
|             return (m_DeviceFlags & DeviceFlags.DisabledInRuntime) == 0;
 | |
|         }
 | |
| 
 | |
|         [Serializable]
 | |
|         [Flags]
 | |
|         internal enum DeviceFlags
 | |
|         {
 | |
|             UpdateBeforeRender = 1 << 0,
 | |
| 
 | |
|             HasStateCallbacks = 1 << 1,
 | |
|             HasControlsWithDefaultState = 1 << 2,
 | |
|             HasDontResetControls = 1 << 10,
 | |
|             HasEventMerger = 1 << 13,
 | |
|             HasEventPreProcessor = 1 << 14,
 | |
| 
 | |
|             Remote = 1 << 3, // It's a local mirror of a device from a remote player connection.
 | |
|             Native = 1 << 4, // It's a device created from data surfaced by NativeInputRuntime.
 | |
| 
 | |
|             DisabledInFrontend = 1 << 5, // Explicitly disabled on the managed side.
 | |
|             DisabledInRuntime = 1 << 7, // Disabled in the native runtime.
 | |
|             DisabledWhileInBackground = 1 << 8, // Disabled while the player is running in the background.
 | |
|             DisabledStateHasBeenQueriedFromRuntime = 1 << 6, // Whether we have fetched the current enable/disable state from the runtime.
 | |
| 
 | |
|             CanRunInBackground = 1 << 11,
 | |
|             CanRunInBackgroundHasBeenQueried = 1 << 12,
 | |
|         }
 | |
| 
 | |
|         internal bool disabledInFrontend
 | |
|         {
 | |
|             get => (m_DeviceFlags & DeviceFlags.DisabledInFrontend) != 0;
 | |
|             set
 | |
|             {
 | |
|                 if (value)
 | |
|                     m_DeviceFlags |= DeviceFlags.DisabledInFrontend;
 | |
|                 else
 | |
|                     m_DeviceFlags &= ~DeviceFlags.DisabledInFrontend;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal bool disabledInRuntime
 | |
|         {
 | |
|             get => (m_DeviceFlags & DeviceFlags.DisabledInRuntime) != 0;
 | |
|             set
 | |
|             {
 | |
|                 if (value)
 | |
|                     m_DeviceFlags |= DeviceFlags.DisabledInRuntime;
 | |
|                 else
 | |
|                     m_DeviceFlags &= ~DeviceFlags.DisabledInRuntime;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal bool disabledWhileInBackground
 | |
|         {
 | |
|             get => (m_DeviceFlags & DeviceFlags.DisabledWhileInBackground) != 0;
 | |
|             set
 | |
|             {
 | |
|                 if (value)
 | |
|                     m_DeviceFlags |= DeviceFlags.DisabledWhileInBackground;
 | |
|                 else
 | |
|                     m_DeviceFlags &= ~DeviceFlags.DisabledWhileInBackground;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal DeviceFlags m_DeviceFlags;
 | |
|         internal int m_DeviceId;
 | |
|         internal int m_ParticipantId;
 | |
|         internal int m_DeviceIndex; // Index in InputManager.m_Devices.
 | |
|         internal InputDeviceDescription m_Description;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Timestamp of last event we received.
 | |
|         /// </summary>
 | |
|         /// <seealso cref="InputEvent.time"/>
 | |
|         internal double m_LastUpdateTimeInternal;
 | |
| 
 | |
|         // Update count corresponding to the current front buffers that are active on the device.
 | |
|         // We use this to know when to flip buffers.
 | |
|         internal uint m_CurrentUpdateStepCount;
 | |
| 
 | |
|         // List of aliases for all controls. Each control gets a slice of this array.
 | |
|         // See 'InputControl.aliases'.
 | |
|         // NOTE: The device's own aliases are part of this array as well.
 | |
|         internal InternedString[] m_AliasesForEachControl;
 | |
| 
 | |
|         // List of usages for all controls. Each control gets a slice of this array.
 | |
|         // See 'InputControl.usages'.
 | |
|         // NOTE: The device's own usages are part of this array as well. They are always
 | |
|         //       at the *end* of the array.
 | |
|         internal InternedString[] m_UsagesForEachControl;
 | |
|         // This one does NOT contain the device itself, i.e. it only contains controls on the device
 | |
|         // and may this be shorter than m_UsagesForEachControl.
 | |
|         internal InputControl[] m_UsageToControl;
 | |
| 
 | |
|         // List of children for all controls. Each control gets a slice of this array.
 | |
|         // See 'InputControl.children'.
 | |
|         // NOTE: The device's own children are part of this array as well.
 | |
|         internal InputControl[] m_ChildrenForEachControl;
 | |
| 
 | |
|         // An ordered list of ints each containing a bit offset into the state of the device (*without* the added global
 | |
|         // offset), a bit count for the size of the state of the control, and an associated index into m_ChildrenForEachControl
 | |
|         // for the corresponding control.
 | |
|         // NOTE: This contains *leaf* controls only.
 | |
|         internal uint[] m_StateOffsetToControlMap;
 | |
| 
 | |
|         // Holds the nodes that represent the tree of memory ranges that each control occupies. This is used when
 | |
|         // determining what controls have changed given a state event or partial state update.
 | |
|         internal ControlBitRangeNode[] m_ControlTreeNodes;
 | |
| 
 | |
|         // An indirection table for control bit range nodes to point at zero or more controls. Indices are used to
 | |
|         // point into the m_ChildrenForEachControl array.
 | |
|         internal ushort[] m_ControlTreeIndices;
 | |
| 
 | |
|         // When a device gets built from a layout, we create a binary tree from its controls where each node in the tree
 | |
|         // represents the range of bits that cover the left or right section of the parent range. For example, starting
 | |
|         // with the entire device state block as the parent, where the state block is 100 bits long, the left node will
 | |
|         // cover from bits 0-50, and the right from bits 51-99. For the left node, we'll get two more child nodes where
 | |
|         // the left will cover bits 0-25, and the right bits 26-49 and so on. Each node will point at any controls that
 | |
|         // either fit exactly into its range, or overlap the splitting point between both nodes. In reality, picking the
 | |
|         // mid-point to split each parent node is a little convoluted and will rarely be the absolute mid-point, but that's
 | |
|         // the basic idea.
 | |
|         //
 | |
|         // At runtime, when state events come in, we can then really quickly perform a bunch of memcmps on both sides of
 | |
|         // the tree and recurse down the branches that have changed. When nodes have controls, we can then check if those
 | |
|         // controls have changes, and mark them as stale so their cached values get updated the next time their values
 | |
|         // are read.
 | |
|         [StructLayout(LayoutKind.Sequential, Pack = 1)]
 | |
|         internal struct ControlBitRangeNode
 | |
|         {
 | |
|             // only store the end bit offset of each range because we always do a full tree traversal so
 | |
|             // the start offset is always calculated at each level.
 | |
|             public ushort endBitOffset;
 | |
| 
 | |
|             // points to the location in the nodes array where the left child of this node lives, or -1 if there
 | |
|             // is no child. The right child is always at the next index.
 | |
|             public short leftChildIndex;
 | |
| 
 | |
|             // each node can point at multiple controls (because multiple controls can use the same range in memory and
 | |
|             // also because of overlaps in bit ranges). The control indicies for each node are stored contiguously in the
 | |
|             // m_ControlTreeIndicies array on the device, which acts as an indirection table, and these two values tell
 | |
|             // us where to start for each node and how many controls this node points at. This is an unsigned short so that
 | |
|             // we could in theory support devices with up to 65535 controls. Each node however can only support 255 controls.
 | |
|             public ushort controlStartIndex;
 | |
|             public byte controlCount;
 | |
| 
 | |
|             public ControlBitRangeNode(ushort endOffset)
 | |
|             {
 | |
|                 controlStartIndex = 0;
 | |
|                 controlCount = 0;
 | |
|                 endBitOffset = endOffset;
 | |
|                 leftChildIndex = -1;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // ATM we pack everything into 32 bits. Given we're operating on bit offsets and counts, this imposes some tight limits
 | |
|         // on controls and their associated state memory. Should this turn out to be a problem, bump m_StateOffsetToControlMap
 | |
|         // to a ulong[] and up the counts here to account for having 64 bits available instead of only 32.
 | |
|         internal const int kControlIndexBits = 10; // 1024 controls max.
 | |
|         internal const int kStateOffsetBits = 13; // 1024 bytes max state size for entire device.
 | |
|         internal const int kStateSizeBits = 9; // 64 bytes max for an individual leaf control.
 | |
| 
 | |
|         internal static uint EncodeStateOffsetToControlMapEntry(uint controlIndex, uint stateOffsetInBits, uint stateSizeInBits)
 | |
|         {
 | |
|             Debug.Assert(kControlIndexBits < 32, $"Expected kControlIndexBits < 32, so we fit into the 32 bit wide bitmask");
 | |
|             Debug.Assert(kStateOffsetBits < 32, $"Expected kStateOffsetBits < 32, so we fit into the 32 bit wide bitmask");
 | |
|             Debug.Assert(kStateSizeBits < 32, $"Expected kStateSizeBits < 32, so we fit into the 32 bit wide bitmask");
 | |
|             Debug.Assert(controlIndex < (1U << kControlIndexBits), "Control index beyond what is supported");
 | |
|             Debug.Assert(stateOffsetInBits < (1U << kStateOffsetBits), "State offset beyond what is supported");
 | |
|             Debug.Assert(stateSizeInBits < (1U << kStateSizeBits), "State size beyond what is supported");
 | |
|             return stateOffsetInBits << (kControlIndexBits + kStateSizeBits) | stateSizeInBits << kControlIndexBits | controlIndex;
 | |
|         }
 | |
| 
 | |
|         internal static void DecodeStateOffsetToControlMapEntry(uint entry, out uint controlIndex,
 | |
|             out uint stateOffset, out uint stateSize)
 | |
|         {
 | |
|             controlIndex = entry & (1U << kControlIndexBits) - 1;
 | |
|             stateOffset = entry >> (kControlIndexBits + kStateSizeBits);
 | |
|             stateSize = (entry >> kControlIndexBits) & (((1U << (kControlIndexBits + kStateSizeBits)) - 1) >> kControlIndexBits);
 | |
|         }
 | |
| 
 | |
|         // NOTE: We don't store processors in a combined array the same way we do for
 | |
|         //       usages and children as that would require lots of casting from 'object'.
 | |
| 
 | |
|         /// <summary>
 | |
|         /// If true, the device has at least one control that has an explicit default state.
 | |
|         /// </summary>
 | |
|         internal bool hasControlsWithDefaultState
 | |
|         {
 | |
|             get => (m_DeviceFlags & DeviceFlags.HasControlsWithDefaultState) == DeviceFlags.HasControlsWithDefaultState;
 | |
|             set
 | |
|             {
 | |
|                 if (value)
 | |
|                     m_DeviceFlags |= DeviceFlags.HasControlsWithDefaultState;
 | |
|                 else
 | |
|                     m_DeviceFlags &= ~DeviceFlags.HasControlsWithDefaultState;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal bool hasDontResetControls
 | |
|         {
 | |
|             get => (m_DeviceFlags & DeviceFlags.HasDontResetControls) == DeviceFlags.HasDontResetControls;
 | |
|             set
 | |
|             {
 | |
|                 if (value)
 | |
|                     m_DeviceFlags |= DeviceFlags.HasDontResetControls;
 | |
|                 else
 | |
|                     m_DeviceFlags &= ~DeviceFlags.HasDontResetControls;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal bool hasStateCallbacks
 | |
|         {
 | |
|             get => (m_DeviceFlags & DeviceFlags.HasStateCallbacks) == DeviceFlags.HasStateCallbacks;
 | |
|             set
 | |
|             {
 | |
|                 if (value)
 | |
|                     m_DeviceFlags |= DeviceFlags.HasStateCallbacks;
 | |
|                 else
 | |
|                     m_DeviceFlags &= ~DeviceFlags.HasStateCallbacks;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal bool hasEventMerger
 | |
|         {
 | |
|             get => (m_DeviceFlags & DeviceFlags.HasEventMerger) == DeviceFlags.HasEventMerger;
 | |
|             set
 | |
|             {
 | |
|                 if (value)
 | |
|                     m_DeviceFlags |= DeviceFlags.HasEventMerger;
 | |
|                 else
 | |
|                     m_DeviceFlags &= ~DeviceFlags.HasEventMerger;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal bool hasEventPreProcessor
 | |
|         {
 | |
|             get => (m_DeviceFlags & DeviceFlags.HasEventPreProcessor) == DeviceFlags.HasEventPreProcessor;
 | |
|             set
 | |
|             {
 | |
|                 if (value)
 | |
|                     m_DeviceFlags |= DeviceFlags.HasEventPreProcessor;
 | |
|                 else
 | |
|                     m_DeviceFlags &= ~DeviceFlags.HasEventPreProcessor;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal void AddDeviceUsage(InternedString usage)
 | |
|         {
 | |
|             var controlUsageCount = m_UsageToControl.LengthSafe();
 | |
|             var totalUsageCount = controlUsageCount + m_UsageCount;
 | |
|             if (m_UsageCount == 0)
 | |
|                 m_UsageStartIndex = totalUsageCount;
 | |
|             ArrayHelpers.AppendWithCapacity(ref m_UsagesForEachControl, ref totalUsageCount, usage);
 | |
|             ++m_UsageCount;
 | |
|         }
 | |
| 
 | |
|         internal void RemoveDeviceUsage(InternedString usage)
 | |
|         {
 | |
|             var controlUsageCount = m_UsageToControl.LengthSafe();
 | |
|             var totalUsageCount = controlUsageCount + m_UsageCount;
 | |
| 
 | |
|             var index = ArrayHelpers.IndexOfValue(m_UsagesForEachControl, usage, m_UsageStartIndex, totalUsageCount);
 | |
|             if (index == -1)
 | |
|                 return;
 | |
| 
 | |
|             Debug.Assert(m_UsageCount > 0);
 | |
|             ArrayHelpers.EraseAtWithCapacity(m_UsagesForEachControl, ref totalUsageCount, index);
 | |
|             --m_UsageCount;
 | |
| 
 | |
|             if (m_UsageCount == 0)
 | |
|                 m_UsageStartIndex = default;
 | |
|         }
 | |
| 
 | |
|         internal void ClearDeviceUsages()
 | |
|         {
 | |
|             for (var i = m_UsageStartIndex; i < m_UsageCount; ++i)
 | |
|                 m_UsagesForEachControl[i] = default;
 | |
|             m_UsageCount = default;
 | |
|         }
 | |
| 
 | |
|         internal bool RequestSync()
 | |
|         {
 | |
|             SetOptimizedControlDataTypeRecursively();
 | |
| 
 | |
|             var syncCommand = RequestSyncCommand.Create();
 | |
|             return device.ExecuteCommand(ref syncCommand) >= 0;
 | |
|         }
 | |
| 
 | |
|         internal bool RequestReset()
 | |
|         {
 | |
|             SetOptimizedControlDataTypeRecursively();
 | |
| 
 | |
|             var resetCommand = RequestResetCommand.Create();
 | |
|             return device.ExecuteCommand(ref resetCommand) >= 0;
 | |
|         }
 | |
| 
 | |
|         internal bool ExecuteEnableCommand()
 | |
|         {
 | |
|             SetOptimizedControlDataTypeRecursively();
 | |
| 
 | |
|             var command = EnableDeviceCommand.Create();
 | |
|             return device.ExecuteCommand(ref command) >= 0;
 | |
|         }
 | |
| 
 | |
|         internal bool ExecuteDisableCommand()
 | |
|         {
 | |
|             var command = DisableDeviceCommand.Create();
 | |
|             return device.ExecuteCommand(ref command) >= 0;
 | |
|         }
 | |
| 
 | |
|         internal void NotifyAdded()
 | |
|         {
 | |
|             OnAdded();
 | |
|         }
 | |
| 
 | |
|         internal void NotifyRemoved()
 | |
|         {
 | |
|             OnRemoved();
 | |
|         }
 | |
| 
 | |
|         internal static TDevice Build<TDevice>(string layoutName = default, string layoutVariants = default, InputDeviceDescription deviceDescription = default, bool noPrecompiledLayouts = false)
 | |
|             where TDevice : InputDevice
 | |
|         {
 | |
|             var internedLayoutName = new InternedString(layoutName);
 | |
| 
 | |
|             if (internedLayoutName.IsEmpty())
 | |
|             {
 | |
|                 internedLayoutName = InputControlLayout.s_Layouts.TryFindLayoutForType(typeof(TDevice));
 | |
|                 if (internedLayoutName.IsEmpty())
 | |
|                     internedLayoutName = new InternedString(typeof(TDevice).Name);
 | |
|             }
 | |
| 
 | |
|             // Fast path: see if we can use a precompiled version.
 | |
|             // NOTE: We currently do not support layout variants with precompiled layouts.
 | |
|             // NOTE: We remove precompiled layouts when they are invalidated by layout changes. So, we don't have to perform
 | |
|             //       checks here.
 | |
|             if (!noPrecompiledLayouts &&
 | |
|                 string.IsNullOrEmpty(layoutVariants) &&
 | |
|                 InputControlLayout.s_Layouts.precompiledLayouts.TryGetValue(internedLayoutName, out var precompiledLayout))
 | |
|             {
 | |
|                 // Yes. This is pretty much a direct new() of the device.
 | |
|                 return (TDevice)precompiledLayout.factoryMethod();
 | |
|             }
 | |
| 
 | |
|             // Slow path: use InputDeviceBuilder to construct the device from the InputControlLayout.
 | |
|             using (InputDeviceBuilder.Ref())
 | |
|             {
 | |
|                 InputDeviceBuilder.instance.Setup(internedLayoutName, new InternedString(layoutVariants),
 | |
|                     deviceDescription: deviceDescription);
 | |
|                 var device = InputDeviceBuilder.instance.Finish();
 | |
|                 if (!(device is TDevice deviceOfType))
 | |
|                     throw new ArgumentException(
 | |
|                         $"Expected device of type '{typeof(TDevice).Name}' but got device of type '{device.GetType().Name}' instead",
 | |
|                         "TDevice");
 | |
| 
 | |
|                 return deviceOfType;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal unsafe void WriteChangedControlStates(byte* deviceStateBuffer, void* statePtr, uint stateSizeInBytes,
 | |
|             uint stateOffsetInDevice)
 | |
|         {
 | |
|             Debug.Assert(m_ControlTreeNodes != null && m_ControlTreeIndices != null);
 | |
| 
 | |
|             if (m_ControlTreeNodes.Length == 0)
 | |
|                 return;
 | |
| 
 | |
|             // if we're dealing with a delta state event or just an individual control update through InputState.ChangeState
 | |
|             // the size of the new data will not be the same size as the device state block, so use the 'partial' change state
 | |
|             // method to update just those controls that overlap with the changed state.
 | |
|             if (m_StateBlock.sizeInBits != stateSizeInBytes * 8)
 | |
|             {
 | |
|                 if (m_ControlTreeNodes[0].leftChildIndex != -1)
 | |
|                     WritePartialChangedControlStatesInternal(statePtr, stateSizeInBytes * 8,
 | |
|                         stateOffsetInDevice * 8, deviceStateBuffer, m_ControlTreeNodes[0], 0);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 if (m_ControlTreeNodes[0].leftChildIndex != -1)
 | |
|                     WriteChangedControlStatesInternal(statePtr, stateSizeInBytes * 8,
 | |
|                         deviceStateBuffer, m_ControlTreeNodes[0], 0);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private unsafe void WritePartialChangedControlStatesInternal(void* statePtr, uint stateSizeInBits,
 | |
|             uint stateOffsetInDeviceInBits, byte* deviceStatePtr, ControlBitRangeNode parentNode, uint startOffset)
 | |
|         {
 | |
|             var leftNode = m_ControlTreeNodes[parentNode.leftChildIndex];
 | |
|             // TODO recheck
 | |
|             if (Math.Max(stateOffsetInDeviceInBits, startOffset) <=
 | |
|                 Math.Min(stateOffsetInDeviceInBits + stateSizeInBits, leftNode.endBitOffset))
 | |
|             {
 | |
|                 var controlEndIndex = leftNode.controlStartIndex + leftNode.controlCount;
 | |
|                 for (int i = leftNode.controlStartIndex; i < controlEndIndex; i++)
 | |
|                 {
 | |
|                     var controlIndex = m_ControlTreeIndices[i];
 | |
|                     m_ChildrenForEachControl[controlIndex].MarkAsStale();
 | |
|                 }
 | |
| 
 | |
|                 if (leftNode.leftChildIndex != -1)
 | |
|                     WritePartialChangedControlStatesInternal(statePtr, stateSizeInBits, stateOffsetInDeviceInBits,
 | |
|                         deviceStatePtr, leftNode, startOffset);
 | |
|             }
 | |
| 
 | |
|             var rightNode = m_ControlTreeNodes[parentNode.leftChildIndex + 1];
 | |
|             // TODO recheck
 | |
|             if (Math.Max(stateOffsetInDeviceInBits, leftNode.endBitOffset) <=
 | |
|                 Math.Min(stateOffsetInDeviceInBits + stateSizeInBits, rightNode.endBitOffset))
 | |
|             {
 | |
|                 var controlEndIndex = rightNode.controlStartIndex + rightNode.controlCount;
 | |
|                 for (int i = rightNode.controlStartIndex; i < controlEndIndex; i++)
 | |
|                 {
 | |
|                     var controlIndex = m_ControlTreeIndices[i];
 | |
|                     m_ChildrenForEachControl[controlIndex].MarkAsStale();
 | |
|                 }
 | |
| 
 | |
|                 if (rightNode.leftChildIndex != -1)
 | |
|                     WritePartialChangedControlStatesInternal(statePtr, stateSizeInBits, stateOffsetInDeviceInBits,
 | |
|                         deviceStatePtr, rightNode, leftNode.endBitOffset);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private void DumpControlBitRangeNode(int nodeIndex, ControlBitRangeNode node, uint startOffset, uint sizeInBits, List<string> output)
 | |
|         {
 | |
|             var names = new List<string>();
 | |
|             for (var i = 0; i < node.controlCount; i++)
 | |
|             {
 | |
|                 var controlIndex = m_ControlTreeIndices[node.controlStartIndex + i];
 | |
|                 var control = m_ChildrenForEachControl[controlIndex];
 | |
|                 names.Add(control.path);
 | |
|             }
 | |
|             var namesStr = string.Join(", ", names);
 | |
|             var children = node.leftChildIndex != -1 ? $" <{node.leftChildIndex}, {node.leftChildIndex + 1}>" : "";
 | |
|             output.Add($"{nodeIndex} [{startOffset}, {startOffset + sizeInBits}]{children}->{namesStr}");
 | |
|         }
 | |
| 
 | |
|         private void DumpControlTree(ControlBitRangeNode parentNode, uint startOffset, List<string> output)
 | |
|         {
 | |
|             var leftNode = m_ControlTreeNodes[parentNode.leftChildIndex];
 | |
|             var rightNode = m_ControlTreeNodes[parentNode.leftChildIndex + 1];
 | |
|             DumpControlBitRangeNode(parentNode.leftChildIndex, leftNode, startOffset, leftNode.endBitOffset - startOffset, output);
 | |
|             DumpControlBitRangeNode(parentNode.leftChildIndex + 1, rightNode, leftNode.endBitOffset, (uint)(rightNode.endBitOffset - leftNode.endBitOffset), output);
 | |
| 
 | |
|             if (leftNode.leftChildIndex != -1)
 | |
|                 DumpControlTree(leftNode, startOffset, output);
 | |
| 
 | |
|             if (rightNode.leftChildIndex != -1)
 | |
|                 DumpControlTree(rightNode, leftNode.endBitOffset, output);
 | |
|         }
 | |
| 
 | |
|         internal string DumpControlTree()
 | |
|         {
 | |
|             var output = new List<string>();
 | |
|             DumpControlTree(m_ControlTreeNodes[0], 0, output);
 | |
|             return string.Join("\n", output);
 | |
|         }
 | |
| 
 | |
|         private unsafe void WriteChangedControlStatesInternal(void* statePtr, uint stateSizeInBits,
 | |
|             byte* deviceStatePtr, ControlBitRangeNode parentNode, uint startOffset)
 | |
|         {
 | |
|             var leftNode = m_ControlTreeNodes[parentNode.leftChildIndex];
 | |
| 
 | |
|             // have any bits in the region defined by the left node changed?
 | |
|             // TODO recheck
 | |
|             if (HasDataChangedInRange(deviceStatePtr, statePtr, startOffset, leftNode.endBitOffset - startOffset + 1))
 | |
|             {
 | |
|                 // update the state of any controls pointed to by the left node
 | |
|                 var controlEndIndex = leftNode.controlStartIndex + leftNode.controlCount;
 | |
|                 for (int i = leftNode.controlStartIndex; i < controlEndIndex; i++)
 | |
|                 {
 | |
|                     var controlIndex = m_ControlTreeIndices[i];
 | |
|                     var control = m_ChildrenForEachControl[controlIndex];
 | |
| 
 | |
|                     // nodes aren't always an exact fit for control memory ranges so check here if the control pointed
 | |
|                     // at by this node has actually changed state so we don't mark controls as stale needlessly.
 | |
|                     // We need to offset the device and new state pointers by the byte offset of the device state block
 | |
|                     // because all controls have this offset baked into them, but deviceStatePtr points at the already
 | |
|                     // offset block of device memory (remember, all devices share one big block of memory) and statePtr
 | |
|                     // points at a block of memory of the same size as the device state.
 | |
|                     if (!control.CompareState(deviceStatePtr - m_StateBlock.byteOffset,
 | |
|                         (byte*)statePtr - m_StateBlock.byteOffset, null))
 | |
|                         control.MarkAsStale();
 | |
|                 }
 | |
| 
 | |
|                 // process the left child node if it exists
 | |
|                 if (leftNode.leftChildIndex != -1)
 | |
|                     WriteChangedControlStatesInternal(statePtr, stateSizeInBits, deviceStatePtr,
 | |
|                         leftNode, startOffset);
 | |
|             }
 | |
| 
 | |
|             // process the right child node if it exists
 | |
|             var rightNode = m_ControlTreeNodes[parentNode.leftChildIndex + 1];
 | |
| 
 | |
|             Debug.Assert(leftNode.endBitOffset + (rightNode.endBitOffset - leftNode.endBitOffset) < m_StateBlock.sizeInBits,
 | |
|                 "Tried to check state memory outside the bounds of the current device.");
 | |
| 
 | |
|             // if no bits in the range defined by the right node have changed, return
 | |
|             // TODO recheck
 | |
|             if (!HasDataChangedInRange(deviceStatePtr, statePtr, leftNode.endBitOffset,
 | |
|                 (uint)(rightNode.endBitOffset - leftNode.endBitOffset + 1)))
 | |
|                 return;
 | |
| 
 | |
|             // update the state of any controls pointed to by the right node
 | |
|             var rightNodeControlEndIndex = rightNode.controlStartIndex + rightNode.controlCount;
 | |
|             for (int i = rightNode.controlStartIndex; i < rightNodeControlEndIndex; i++)
 | |
|             {
 | |
|                 var controlIndex = m_ControlTreeIndices[i];
 | |
|                 var control = m_ChildrenForEachControl[controlIndex];
 | |
| 
 | |
|                 if (!control.CompareState(deviceStatePtr - m_StateBlock.byteOffset,
 | |
|                     (byte*)statePtr - m_StateBlock.byteOffset, null))
 | |
|                     control.MarkAsStale();
 | |
|             }
 | |
| 
 | |
|             if (rightNode.leftChildIndex != -1)
 | |
|                 WriteChangedControlStatesInternal(statePtr, stateSizeInBits, deviceStatePtr,
 | |
|                     rightNode, leftNode.endBitOffset);
 | |
|         }
 | |
| 
 | |
|         private static unsafe bool HasDataChangedInRange(byte* deviceStatePtr, void* statePtr, uint startOffset, uint sizeInBits)
 | |
|         {
 | |
|             if (sizeInBits == 1)
 | |
|                 return MemoryHelpers.ReadSingleBit(deviceStatePtr, startOffset) !=
 | |
|                     MemoryHelpers.ReadSingleBit(statePtr, startOffset);
 | |
| 
 | |
|             return !MemoryHelpers.MemCmpBitRegion(deviceStatePtr, statePtr,
 | |
|                 startOffset, sizeInBits);
 | |
|         }
 | |
|     }
 | |
| }
 |