2023-03-28 13:24:16 -04:00
using System ;
2023-05-07 18:43:11 -04:00
using System.Collections.Generic ;
using System.Runtime.InteropServices ;
2023-03-28 13:24:16 -04:00
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 ;
2023-05-07 18:43:11 -04:00
// 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 ;
}
}
2023-03-28 13:24:16 -04:00
// 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 < ( 1 U < < kControlIndexBits ) , "Control index beyond what is supported" ) ;
Debug . Assert ( stateOffsetInBits < ( 1 U < < kStateOffsetBits ) , "State offset beyond what is supported" ) ;
Debug . Assert ( stateSizeInBits < ( 1 U < < 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 & ( 1 U < < kControlIndexBits ) - 1 ;
stateOffset = entry > > ( kControlIndexBits + kStateSizeBits ) ;
stateSize = ( entry > > kControlIndexBits ) & ( ( ( 1 U < < ( 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 ( )
{
2023-05-07 18:43:11 -04:00
SetOptimizedControlDataTypeRecursively ( ) ;
2023-03-28 13:24:16 -04:00
var syncCommand = RequestSyncCommand . Create ( ) ;
return device . ExecuteCommand ( ref syncCommand ) > = 0 ;
}
internal bool RequestReset ( )
{
2023-05-07 18:43:11 -04:00
SetOptimizedControlDataTypeRecursively ( ) ;
2023-03-28 13:24:16 -04:00
var resetCommand = RequestResetCommand . Create ( ) ;
return device . ExecuteCommand ( ref resetCommand ) > = 0 ;
}
internal bool ExecuteEnableCommand ( )
{
2023-05-07 18:43:11 -04:00
SetOptimizedControlDataTypeRecursively ( ) ;
2023-03-28 13:24:16 -04:00
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 ;
}
}
2023-05-07 18:43:11 -04:00
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 ) ;
}
2023-03-28 13:24:16 -04:00
}
}