| 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 (InputState.currentUpdateType == InputUpdateType.Editor && (m_DeviceFlags & DeviceFlags.DisabledWhileInBackground) != 0) | |
| return true; | |
| 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. | |
| 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. | |
| 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. | |
| [] | |
| 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; | |
| } | |
| [] | |
| [] | |
| 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. | |
| [] | |
| 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); | |
| } | |
| } | |
| } | |