using System; using System.Collections.Generic; using System.Diagnostics; using UnityEngine.InputSystem.Controls; using UnityEngine.InputSystem.LowLevel; using UnityEngine.InputSystem.Utilities; using Unity.Collections.LowLevel.Unsafe; using UnityEngine.InputSystem.Layouts; ////REVIEW: should EvaluateMagnitude() be called EvaluateActuation() or something similar? ////REVIEW: as soon as we gain the ability to have blittable type constraints, InputControl<TValue> should be constrained such ////REVIEW: Reading and writing is asymmetric. Writing does not involve processors, reading does. ////REVIEW: While the arrays used by controls are already nicely centralized on InputDevice, InputControls still //// hold a bunch of reference data that requires separate scanning. Can we move *all* reference data to arrays //// on InputDevice and make InputControls reference-free? Most challenging thing probably is getting rid of //// the InputDevice reference itself. ////REVIEW: how do we do stuff like smoothing over time? ////TODO: allow easier access to the default state such that you can easily create a state event containing only default state ////TODO: come up with a way where we do ReadValue on the most common forms/setups of controls and not have any virtual method dispatch but //// rather go with minimal overhead directly to reading out memory //// (this should at least cover FLT, single BIT, and INT controls; and should be able to apply the common transformations //// as per AxisControl) namespace UnityEngine.InputSystem { /// <summary> /// A typed and named source of input values in a hierarchy of controls. /// </summary> /// <remarks> /// Controls can have children which in turn may have children. At the root of the child /// hierarchy is always an <see cref="InputDevice"/> (which themselves are InputControls). /// /// Controls can be looked up by their <see cref="path"/> (see <see cref="InputControlPath.TryFindControl"/>). /// /// Each control must have a unique <see cref="name"/> within the <see cref="children"/> of /// its <see cref="parent"/>. Multiple names can be assigned to controls using aliases (see /// <see cref="aliases"/>). Name lookup is case-insensitive. /// /// For display purposes, a control may have a separate <see cref="displayName"/>. This name /// will usually correspond to what the control is caused on the actual underlying hardware. /// For example, on an Xbox gamepad, the control with the name "buttonSouth" will have a display /// name of "A". Controls that have very long display names may also have a <see cref="shortDisplayName"/>. /// This is the case for the "Left Button" on the <see cref="Mouse"/>, for example, which is /// commonly abbreviated "LMB". /// /// In addition to names, a control may have usages associated with it (see <see cref="usages"/>). /// A usage indicates how a control is meant to be used. For example, a button can be assigned /// the "PrimaryAction" usage to indicate it is the primary action button the device. Within a /// device, usages have to be unique. See <see cref="CommonUsages"/> for a list of standardized usages. /// /// Controls do not actually store values. Instead, every control receives an <see cref="InputStateBlock"/> /// which, after the control's device has been added to the system, is used to read out values /// from the device's backing store. This backing store is referred to as "state" in the API /// as opposed to "values" which represent the data resulting from reading state. The format that /// each control stores state in is specific to the control. It can vary not only between controls /// of different types but also between controls of the same type. An <see cref="AxisControl"/>, /// for example, can be stored as a float or as a byte or in a number of other formats. <see cref="stateBlock"/> /// identifies both where the control stores its state as well as the format it stores it in. /// /// Controls are generally not created directly but are created internally by the input system /// from data known as "layouts" (see <see cref="InputControlLayout"/>). Each such layout describes /// the setup of a specific hierarchy of controls. The system internally maintains a registry of /// layouts and produces devices and controls from them as needed. The layout that a control has /// been created from can be queried using <see cref="layout"/>. For most purposes, the intricacies /// of the control layout mechanisms can be ignored and it is sufficient to know the names of a /// small set of common device layouts such as "Keyboard", "Mouse", "Gamepad", and "Touchscreen". /// /// Each control has a single, fixed value type. The type can be queried at runtime using /// <see cref="valueType"/>. Most types of controls are derived from <see cref="InputControl{TValue}"/> /// which has APIs specific to the type of value of the control (e.g. <see cref="InputControl{TValue}.ReadValue()"/>. /// /// The following example demonstrates various common operations performed on input controls: /// /// <example> /// <code> /// // Look up dpad/up control on current gamepad. /// var dpadUpControl = Gamepad.current["dpad/up"]; /// /// // Look up the back button on the current gamepad. /// var backButton = Gamepad.current["{Back}"]; /// /// // Look up all dpad/up controls on all gamepads in the system. /// using (var controls = InputSystem.FindControls("<Gamepad>/dpad/up")) /// Debug.Log($"Found {controls.Count} controls"); /// /// // Display the value of all controls on the current gamepad. /// foreach (var control in Gamepad.current.allControls) /// Debug.Log(controls.ReadValueAsObject()); /// /// // Track the value of the left stick on the current gamepad over time. /// var leftStickHistory = new InputStateHistory(Gamepad.current.leftStick); /// leftStickHistory.Enable(); /// </code> /// </example> /// <example> /// </example> /// </remarks> /// <see cref="InputControl{TValue}"/> /// <seealso cref="InputDevice"/> /// <seealso cref="InputControlPath"/> /// <seealso cref="InputStateBlock"/> [DebuggerDisplay("{DebuggerDisplay(),nq}")] public abstract class InputControl { /// <summary> /// The name of the control, i.e. the final name part in its path. /// </summary> /// <remarks> /// Names of controls must be unique within the context of their parent. /// /// Note that this is the name of the control as assigned internally (like "buttonSouth") /// and not necessarily a good display name. Use <see cref="displayName"/> for /// getting more readable names for display purposes (where available). /// /// Lookup of names is case-insensitive. /// /// This is set from the name of the control in the layout. /// </remarks> /// <seealso cref="path"/> /// <seealso cref="aliases"/> /// <seealso cref="InputControlAttribute.name"/> /// <seealso cref="InputControlLayout.ControlItem.name"/> public string name => m_Name; ////TODO: protect against empty strings /// <summary> /// The text to display as the name of the control. /// </summary> /// <remarks> /// Note that the display name of a control may change over time. For example, when changing /// from a QWERTY keyboard layout to an AZERTY keyboard layout, the "q" key (which will keep /// that <see cref="name"/>) will change its display name from "q" to "a". /// /// By default, a control's display name will come from its layout. If it is not assigned /// a display name there, the display name will default to <see cref="name"/>. However, specific /// controls may override this behavior. <see cref="KeyControl"/>, for example, will set the /// display name to the actual key name corresponding to the current keyboard layout. /// /// For nested controls, the display name will include the display names of all parent controls, /// i.e. the display name will fully identify the control on the device. For example, the display /// name for the left D-Pad button on a gamepad is "D-Pad Left" and not just "Left". /// </remarks> /// <seealso cref="shortDisplayName"/> public string displayName { get { RefreshConfigurationIfNeeded(); if (m_DisplayName != null) return m_DisplayName; if (m_DisplayNameFromLayout != null) return m_DisplayNameFromLayout; return m_Name; } // This is not public as a domain reload will wipe the change. This should really // come from the control itself *if* the control wants to have a custom display name // not driven by its layout. protected set => m_DisplayName = value; } /// <summary> /// An alternate, abbreviated <see cref="displayName"/> (for example "LMB" instead of "Left Button"). /// </summary> /// <remarks> /// If the control has no abbreviated version, this will be null. Note that this behavior is different /// from <see cref="displayName"/> which will fall back to <see cref="name"/> if no display name has /// been assigned to the control. /// /// For nested controls, the short display name will include the short display names of all parent controls, /// that is, the display name will fully identify the control on the device. For example, the display /// name for the left D-Pad button on a gamepad is "D-Pad \u2190" and not just "\u2190". Note that if a parent /// control has no short name, its long name will be used instead. /// </remarks> /// <seealso cref="displayName"/> public string shortDisplayName { get { RefreshConfigurationIfNeeded(); if (m_ShortDisplayName != null) return m_ShortDisplayName; if (m_ShortDisplayNameFromLayout != null) return m_ShortDisplayNameFromLayout; return null; } protected set => m_ShortDisplayName = value; } /// <summary> /// Full path all the way from the root. /// </summary> /// <remarks> /// This will always be the "effective" path of the control, i.e. it will not contain /// elements such as usages (<c>"{Back}"</c>) and other elements that can be part of /// control paths used for matching. Instead, this property will always be a simple /// linear ordering of names leading from the device at the top to the control with each /// element being separated by a forward slash (<c>/</c>). /// /// Allocates on first hit. Paths are not created until someone asks for them. /// /// <example> /// Example: "/gamepad/leftStick/x" /// </example> /// </remarks> /// <seealso cref="InputControlPath"/> public string path { get { if (m_Path == null) m_Path = InputControlPath.Combine(m_Parent, m_Name); return m_Path; } } /// <summary> /// Layout the control is based on. /// </summary> /// <remarks> /// This is the layout name rather than a reference to an <see cref="InputControlLayout"/> as /// we only create layout instances during device creation and treat them /// as temporaries in general so as to not waste heap space during normal operation. /// </remarks> public string layout => m_Layout; /// <summary> /// Semicolon-separated list of variants of the control layout or "default". /// </summary> /// <example> /// "Lefty" when using the "Lefty" gamepad layout. /// </example> public string variants => m_Variants; /// <summary> /// The device that this control is a part of. /// </summary> /// <remarks> /// This is the root of the control hierarchy. For the device at the root, this /// will point to itself. /// </remarks> /// <seealso cref="InputDevice.allControls"/> public InputDevice device => m_Device; /// <summary> /// The immediate parent of the control or null if the control has no parent /// (which, once fully constructed) will only be the case for InputDevices). /// </summary> /// <seealso cref="children"/> public InputControl parent => m_Parent; /// <summary> /// List of immediate children. /// </summary> /// <remarks> /// Does not allocate. /// </remarks> /// <seealso cref="parent"/> public ReadOnlyArray<InputControl> children => new ReadOnlyArray<InputControl>(m_Device.m_ChildrenForEachControl, m_ChildStartIndex, m_ChildCount); /// <summary> /// List of usage tags associated with the control. /// </summary> /// <remarks> /// Usages apply "semantics" to a control. Whereas the name of a control identifies a particular /// "endpoint" within the control hierarchy, the usages of a control identify particular roles /// of specific control. A simple example is <see cref="CommonUsages.Back"/> which identifies a /// control generally used to move backwards in the navigation history of a UI. On a keyboard, /// it is the escape key that generally fulfills this role whereas on a gamepad, it is generally /// the "B" / "Circle" button. Some devices may not have a control that generally fulfills this /// function and thus may not have any control with the "Back" usage. /// /// By looking up controls by usage rather than by name, it is possible to locate the correct /// control to use for certain standardized situation without having to know the particulars of /// the device or platform. /// /// <example> /// <code> /// // Bind to any control which is tagged with the "Back" usage on any device. /// var backAction = new InputAction(binding: "*/{Back}"); /// </code> /// </example> /// /// Note that usages on devices work slightly differently than usages of controls on devices. /// They are also queried through this property but unlike the usages of controls, the set of /// usages of a device can be changed dynamically as the role of the device changes. For details, /// see <see cref="InputSystem.SetDeviceUsage(InputDevice,string)"/>. Controls, on the other hand, /// can currently only be assigned usages through layouts (<see cref="InputControlAttribute.usage"/> /// or <see cref="InputControlAttribute.usages"/>). /// </remarks> /// <seealso cref="InputControlAttribute.usage"/> /// <seealso cref="InputControlAttribute.usages"/> /// <seealso cref="InputSystem.SetDeviceUsage(InputDevice,string)"/> /// <seealso cref="InputSystem.AddDeviceUsage(InputDevice,string)"/> /// <seealso cref="InputSystem.RemoveDeviceUsage(InputDevice,string)"/> /// <seealso cref="CommonUsages"/> public ReadOnlyArray<InternedString> usages => new ReadOnlyArray<InternedString>(m_Device.m_UsagesForEachControl, m_UsageStartIndex, m_UsageCount); // List of alternate names for the control. public ReadOnlyArray<InternedString> aliases => new ReadOnlyArray<InternedString>(m_Device.m_AliasesForEachControl, m_AliasStartIndex, m_AliasCount); // Information about where the control stores its state. public InputStateBlock stateBlock => m_StateBlock; /// <summary> /// Whether the control is considered noisy. /// </summary> /// <value>True if the control produces noisy input.</value> /// <remarks> /// A control is considered "noisy" if it produces different values without necessarily requiring user /// interaction. A good example are sensors (see <see cref="Sensor"/>). For example, the PS4 controller /// which has a gyroscope sensor built into the device. Whereas sticks and buttons on the device require /// user interaction to produce non-default values, the gyro will produce varying values even if the /// device just sits there without user interaction. /// /// The value of this property is determined by the layout (<see cref="InputControlLayout"/>) that the /// control has been built from. /// /// Note that for devices (<see cref="InputDevice"/>) this property is true if any control on the device /// is marked as noisy. /// /// The primary effect of being noise is on <see cref="InputDevice.MakeCurrent"/> and /// on interactive rebinding (see <see cref="InputActionRebindingExtensions.RebindingOperation"/>). /// However, being noisy also affects automatic resetting of controls that happens when the application /// loses focus. While other controls are reset to their default value (except if <c>Application.runInBackground</c> /// is true and the device the control belongs to is marked as <see cref="InputDevice.canRunInBackground"/>), /// noisy controls will not be reset but rather remain at their current value. This is based on the assumption /// that noisy controls most often represent sensor values and snapping the last sampling value back to default /// will usually have undesirable effects on an application's simulation logic. /// </remarks> /// <seealso cref="InputControlLayout.ControlItem.isNoisy"/> /// <seealso cref="InputControlAttribute.noisy"/> public bool noisy { get => (m_ControlFlags & ControlFlags.IsNoisy) != 0; internal set { if (value) { m_ControlFlags |= ControlFlags.IsNoisy; // Making a control noisy makes all its children noisy. var list = children; for (var i = 0; i < list.Count; ++i) { if (null != list[i]) list[i].noisy = true; } } else m_ControlFlags &= ~ControlFlags.IsNoisy; } } /// <summary> /// Whether the control is considered synthetic. /// </summary> /// <value>True if the control does not represent an actual physical control on the device.</value> /// <remarks> /// A control is considered "synthetic" if it does not correspond to an actual, physical control on the /// device. An example for this is <see cref="Keyboard.anyKey"/> or the up/down/left/right buttons added /// by <see cref="StickControl"/>. /// /// The value of this property is determined by the layout (<see cref="InputControlLayout"/>) that the /// control has been built from. /// /// The primary effect of being synthetic is in interactive rebinding (see /// <see cref="InputActionRebindingExtensions.RebindingOperation"/>) where non-synthetic /// controls will be favored over synthetic ones. This means, for example, that if both /// <c>"<Gamepad>/leftStick/x"</c> and <c>"<Gamepad>/leftStick/left"</c> are /// suitable picks, <c>"<Gamepad>/leftStick/x"</c> will be favored as it represents /// input from an actual physical control whereas <c>"<Gamepad>/leftStick/left"</c> /// represents input from a made-up control. If, however, the "left" button is the only /// viable pick, it will be accepted. /// </remarks> /// <seealso cref="InputControlLayout.ControlItem.isSynthetic"/> /// <seealso cref="InputControlAttribute.synthetic"/> public bool synthetic { get => (m_ControlFlags & ControlFlags.IsSynthetic) != 0; internal set { if (value) m_ControlFlags |= ControlFlags.IsSynthetic; else m_ControlFlags &= ~ControlFlags.IsSynthetic; } } /// <summary> /// Fetch a control from the control's hierarchy by name. /// </summary> /// <remarks> /// Note that path matching is case-insensitive. /// </remarks> /// <example> /// <code> /// gamepad["leftStick"] // Returns Gamepad.leftStick /// gamepad["leftStick/x"] // Returns Gamepad.leftStick.x /// gamepad["{PrimaryAction}"] // Returns the control with PrimaryAction usage, that is, Gamepad.aButton /// </code> /// </example> /// <exception cref="KeyNotFoundException"><paramref name="path"/> cannot be found.</exception> /// <seealso cref="InputControlPath"/> /// <seealso cref="path"/> /// <seealso cref="TryGetChildControl"/> public InputControl this[string path] { get { var control = InputControlPath.TryFindChild(this, path); if (control == null) throw new KeyNotFoundException( $"Cannot find control '{path}' as child of '{this}'"); return control; } } /// <summary> /// Returns the underlying value type of this control. /// </summary> /// <value>Type of values produced by the control.</value> /// <remarks> /// This is the type of values that are returned when reading the current value of a control /// or when reading a value of a control from an event. /// </remarks> /// <seealso cref="valueSizeInBytes"/> /// <seealso cref="ReadValueFromStateAsObject"/> public abstract Type valueType { get; } /// <summary> /// Size in bytes of values that the control returns. /// </summary> /// <seealso cref="valueType"/> public abstract int valueSizeInBytes { get; } /// <summary> /// Return a string representation of the control useful for debugging. /// </summary> /// <returns>A string representation of the control.</returns> public override string ToString() { return $"{layout}:{path}"; } private string DebuggerDisplay() { // If the device hasn't been added, don't try to read the control's value. if (!device.added) return ToString(); // ReadValueAsObject might throw. Revert to just ToString() in that case. try { return $"{layout}:{path}={this.ReadValueAsObject()}"; } catch (Exception) { return ToString(); } } ////REVIEW: The -1 behavior seems bad; probably better to just return 1 for controls that do not support finer levels of actuation /// <summary> /// Compute an absolute, normalized magnitude value that indicates the extent to which the control /// is actuated. /// </summary> /// <returns>Amount of actuation of the control or -1 if it cannot be determined.</returns> /// <remarks> /// Magnitudes do not make sense for all types of controls. For example, for a control that represents /// an enumeration of values (such as <see cref="TouchPhaseControl"/>), there is no meaningful /// linear ordering of values (one could derive a linear ordering through the actual enum values but /// their assignment may be entirely arbitrary; it is unclear whether a state of <see cref="TouchPhase.Canceled"/> /// has a higher or lower "magnitude" as a state of <see cref="TouchPhase.Began"/>). /// /// Controls that have no meaningful magnitude will return -1 when calling this method. Any negative /// return value should be considered an invalid value. /// </remarks> /// <seealso cref="EvaluateMagnitude(void*)"/> public unsafe float EvaluateMagnitude() { return EvaluateMagnitude(currentStatePtr); } /// <summary> /// Compute an absolute, normalized magnitude value that indicates the extent to which the control /// is actuated in the given state. /// </summary> /// <param name="statePtr">State containing the control's <see cref="stateBlock"/>.</param> /// <returns>Amount of actuation of the control or -1 if it cannot be determined.</returns> /// <seealso cref="EvaluateMagnitude()"/> /// <seealso cref="stateBlock"/> public virtual unsafe float EvaluateMagnitude(void* statePtr) { return -1; } public abstract unsafe object ReadValueFromBufferAsObject(void* buffer, int bufferSize); /// <summary> /// Read the control's final, processed value from the given state and return the value as an object. /// </summary> /// <param name="statePtr"></param> /// <returns>The control's value as stored in <paramref name="statePtr"/>.</returns> /// <remarks> /// This method allocates GC memory and should not be used during normal gameplay operation. /// </remarks> /// <exception cref="ArgumentNullException"><paramref name="statePtr"/> is null.</exception> /// <seealso cref="ReadValueFromStateIntoBuffer"/> public abstract unsafe object ReadValueFromStateAsObject(void* statePtr); /// <summary> /// Read the control's final, processed value from the given state and store it in the given buffer. /// </summary> /// <param name="statePtr">State to read the value for the control from.</param> /// <param name="bufferPtr">Buffer to store the value in.</param> /// <param name="bufferSize">Size of <paramref name="bufferPtr"/> in bytes. Must be at least <see cref="valueSizeInBytes"/>. /// If it is smaller, <see cref="ArgumentException"/> will be thrown.</param> /// <exception cref="ArgumentNullException"><paramref name="statePtr"/> is null, or <paramref name="bufferPtr"/> is null.</exception> /// <exception cref="ArgumentException"><paramref name="bufferSize"/> is smaller than <see cref="valueSizeInBytes"/>.</exception> /// <seealso cref="ReadValueFromStateAsObject"/> /// <seealso cref="WriteValueFromBufferIntoState"/> public abstract unsafe void ReadValueFromStateIntoBuffer(void* statePtr, void* bufferPtr, int bufferSize); /// <summary> /// Read a value from the given memory and store it as state. /// </summary> /// <param name="bufferPtr">Memory containing value.</param> /// <param name="bufferSize">Size of <paramref name="bufferPtr"/> in bytes. Must be at least <see cref="valueSizeInBytes"/>.</param> /// <param name="statePtr">State containing the control's <see cref="stateBlock"/>. Will receive the state /// as converted from the given value.</param> /// <remarks> /// Writing values will NOT apply processors to the given value. This can mean that when reading a value /// from a control after it has been written to its state, the resulting value differs from what was /// written. /// </remarks> /// <exception cref="NotSupportedException">The control does not support writing. This is the case, for /// example, that compute values (such as the magnitude of a vector).</exception> /// <seealso cref="ReadValueFromStateIntoBuffer"/> /// <seealso cref="WriteValueFromObjectIntoState"/> public virtual unsafe void WriteValueFromBufferIntoState(void* bufferPtr, int bufferSize, void* statePtr) { throw new NotSupportedException( $"Control '{this}' does not support writing"); } /// <summary> /// Read a value object and store it as state in the given memory. /// </summary> /// <param name="value">Value for the control.</param> /// <param name="statePtr">State containing the control's <see cref="stateBlock"/>. Will receive /// the state state as converted from the given value.</param> /// <remarks> /// Writing values will NOT apply processors to the given value. This can mean that when reading a value /// from a control after it has been written to its state, the resulting value differs from what was /// written. /// </remarks> /// <exception cref="NotSupportedException">The control does not support writing. This is the case, for /// example, that compute values (such as the magnitude of a vector).</exception> /// <seealso cref="WriteValueFromBufferIntoState"/> public virtual unsafe void WriteValueFromObjectIntoState(object value, void* statePtr) { throw new NotSupportedException( $"Control '{this}' does not support writing"); } /// <summary> /// Compare the value of the control as read from <paramref name="firstStatePtr"/> to that read from /// <paramref name="secondStatePtr"/> and return true if they are equal. /// </summary> /// <param name="firstStatePtr">Memory containing the control's <see cref="stateBlock"/>.</param> /// <param name="secondStatePtr">Memory containing the control's <see cref="stateBlock"/></param> /// <returns>True if the value of the control is equal in both <paramref name="firstStatePtr"/> and /// <paramref name="secondStatePtr"/>.</returns> /// <remarks> /// Unlike <see cref="CompareValue"/>, this method will have to do more than just compare the memory /// for the control in the two state buffers. It will have to read out state for the control and run /// the full processing machinery for the control to turn the state into a final, processed value. /// CompareValue is thus more costly than <see cref="CompareValue"/>. /// /// This method will apply epsilons (<see cref="Mathf.Epsilon"/>) when comparing floats. /// </remarks> /// <seealso cref="CompareValue"/> public abstract unsafe bool CompareValue(void* firstStatePtr, void* secondStatePtr); /// <summary> /// Try to find a child control matching the given path. /// </summary> /// <param name="path">A control path. See <see cref="InputControlPath"/>.</param> /// <returns>The first direct or indirect child control that matches the given <paramref name="path"/> /// or null if no control was found to match.</returns> /// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c> or empty.</exception> /// <remarks> /// Note that if the given path matches multiple child controls, only the first control /// encountered in the search will be returned. /// /// <example> /// <code> /// // Returns the leftStick control of the current gamepad. /// Gamepad.current.TryGetChildControl("leftStick"); /// /// // Returns the X axis control of the leftStick on the current gamepad. /// Gamepad.current.TryGetChildControl("leftStick/x"); /// /// // Returns the first control ending with "stick" in its name. Note that it /// // undetermined whether this is leftStick or rightStick (or even another stick /// // added by the given gamepad). /// Gamepad.current.TryGetChildControl("*stick"); /// </code> /// </example> /// /// This method is equivalent to calling <see cref="InputControlPath.TryFindChild"/>. /// </remarks> public InputControl TryGetChildControl(string path) { if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); return InputControlPath.TryFindChild(this, path); } public TControl TryGetChildControl<TControl>(string path) where TControl : InputControl { if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); var control = TryGetChildControl(path); if (control == null) return null; var controlOfType = control as TControl; if (controlOfType == null) throw new InvalidOperationException( $"Expected control '{path}' to be of type '{typeof(TControl).Name}' but is of type '{control.GetType().Name}' instead!"); return controlOfType; } public InputControl GetChildControl(string path) { if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); var control = TryGetChildControl(path); if (control == null) throw new ArgumentException($"Cannot find input control '{MakeChildPath(path)}'", nameof(path)); return control; } public TControl GetChildControl<TControl>(string path) where TControl : InputControl { var control = GetChildControl(path); if (!(control is TControl controlOfType)) throw new ArgumentException( $"Expected control '{path}' to be of type '{typeof(TControl).Name}' but is of type '{control.GetType().Name}' instead!", nameof(path)); return controlOfType; } protected InputControl() { // Set defaults for state block setup. Subclasses may override. m_StateBlock.byteOffset = InputStateBlock.AutomaticOffset; // Request automatic layout by default. } /// <summary> /// Perform final initialization tasks after the control hierarchy has been put into place. /// </summary> /// <remarks> /// This method can be overridden to perform control- or device-specific setup work. The most /// common use case is for looking up child controls and storing them in local getters. /// /// <example> /// <code> /// public class MyDevice : InputDevice /// { /// public ButtonControl button { get; private set; } /// public AxisControl axis { get; private set; } /// /// protected override void OnFinishSetup() /// { /// // Cache controls in getters. /// button = GetChildControl("button"); /// axis = GetChildControl("axis"); /// } /// } /// </code> /// </example> /// </remarks> protected virtual void FinishSetup() { } /// <summary> /// Call <see cref="RefreshConfiguration"/> if the configuration has in the interim been invalidated /// by a <see cref="DeviceConfigurationEvent"/>. /// </summary> /// <remarks> /// This method is only relevant if you are implementing your own devices or new /// types of controls which are fetching configuration data from the devices (such /// as <see cref="KeyControl"/> which is fetching display names for individual keys /// from the underlying platform). /// /// This method should be called if you are accessing cached data set up by /// <see cref="RefreshConfiguration"/>. /// /// <example> /// <code> /// // Let's say your device has an associated orientation which it can be held with /// // and you want to surface both as a property and as a usage on the device. /// // Whenever your backend code detects a change in orientation, it should send /// // a DeviceConfigurationEvent to your device to signal that the configuration /// // of the device has changed. You can then implement RefreshConfiguration() to /// // read out and update the device orientation on the managed InputDevice instance. /// public class MyDevice : InputDevice /// { /// public enum Orientation /// { /// Horizontal, /// Vertical, /// } /// /// private Orientation m_Orientation; /// public Orientation orientation /// { /// get /// { /// // Call RefreshOrientation if the configuration of the device has been /// // invalidated since last time we initialized m_Orientation. /// RefreshConfigurationIfNeeded(); /// return m_Orientation; /// } /// } /// protected override void RefreshConfiguration() /// { /// // Fetch the current orientation from the backend. How you do this /// // depends on your device. Using DeviceCommands is one way. /// var fetchOrientationCommand = new FetchOrientationCommand(); /// ExecuteCommand(ref fetchOrientationCommand); /// m_Orientation = fetchOrientation; /// /// // Reflect the orientation on the device. /// switch (m_Orientation) /// { /// case Orientation.Vertical: /// InputSystem.RemoveDeviceUsage(this, s_Horizontal); /// InputSystem.AddDeviceUsage(this, s_Vertical); /// break; /// /// case Orientation.Horizontal: /// InputSystem.RemoveDeviceUsage(this, s_Vertical); /// InputSystem.AddDeviceUsage(this, s_Horizontal); /// break; /// } /// } /// /// private static InternedString s_Vertical = new InternedString("Vertical"); /// private static InternedString s_Horizontal = new InternedString("Horizontal"); /// } /// </code> /// </example> /// </remarks> /// <seealso cref="RefreshConfiguration"/> protected void RefreshConfigurationIfNeeded() { if (!isConfigUpToDate) { RefreshConfiguration(); isConfigUpToDate = true; } } protected virtual void RefreshConfiguration() { } ////TODO: drop protected access protected internal InputStateBlock m_StateBlock; ////REVIEW: shouldn't these sit on the device? protected internal unsafe void* currentStatePtr => InputStateBuffers.GetFrontBufferForDevice(GetDeviceIndex()); protected internal unsafe void* previousFrameStatePtr => InputStateBuffers.GetBackBufferForDevice(GetDeviceIndex()); protected internal unsafe void* defaultStatePtr => InputStateBuffers.s_DefaultStateBuffer; /// <summary> /// Return the memory that holds the noise mask for the control. /// </summary> /// <value>Noise bit mask for the control.</value> /// <remarks> /// Like with all state blocks, the specific memory block for the control is found at the memory /// region specified by <see cref="stateBlock"/>. /// /// The noise mask can be overlaid as a bit mask over the state for the control. When doing so, all state /// that is noise will be masked out whereas all state that isn't will come through unmodified. In other words, /// any bit that is set in the noise mask indicates that the corresponding bit in the control's state memory /// is noise. /// </remarks> /// <seealso cref="noisy"/> protected internal unsafe void* noiseMaskPtr => InputStateBuffers.s_NoiseMaskBuffer; /// <summary> /// The offset of this control's state relative to its device root. /// </summary> /// <remarks> /// Once a device has been added to the system, its state block will get allocated /// in the global state buffers and the offset of the device's state block will /// get baked into all of the controls on the device. This property always returns /// the "unbaked" offset. /// </remarks> protected internal uint stateOffsetRelativeToDeviceRoot { get { var deviceStateOffset = device.m_StateBlock.byteOffset; Debug.Assert(deviceStateOffset <= m_StateBlock.byteOffset); return m_StateBlock.byteOffset - deviceStateOffset; } } // This data is initialized by InputDeviceBuilder. internal InternedString m_Name; internal string m_Path; internal string m_DisplayName; // Display name set by the control itself (may be null). internal string m_DisplayNameFromLayout; // Display name coming from layout (may be null). internal string m_ShortDisplayName; // Short display name set by the control itself (may be null). internal string m_ShortDisplayNameFromLayout; // Short display name coming from layout (may be null). internal InternedString m_Layout; internal InternedString m_Variants; internal InputDevice m_Device; internal InputControl m_Parent; internal int m_UsageCount; internal int m_UsageStartIndex; internal int m_AliasCount; internal int m_AliasStartIndex; internal int m_ChildCount; internal int m_ChildStartIndex; internal ControlFlags m_ControlFlags; ////REVIEW: store these in arrays in InputDevice instead? internal PrimitiveValue m_DefaultState; internal PrimitiveValue m_MinValue; internal PrimitiveValue m_MaxValue; [Flags] internal enum ControlFlags { ConfigUpToDate = 1 << 0, IsNoisy = 1 << 1, IsSynthetic = 1 << 2, IsButton = 1 << 3, DontReset = 1 << 4, SetupFinished = 1 << 5, // Can't be modified once this is set. UsesStateFromOtherControl = 1 << 6, } internal bool isSetupFinished { get => (m_ControlFlags & ControlFlags.SetupFinished) == ControlFlags.SetupFinished; set { if (value) m_ControlFlags |= ControlFlags.SetupFinished; else m_ControlFlags &= ~ControlFlags.SetupFinished; } } internal bool isButton { get => (m_ControlFlags & ControlFlags.IsButton) == ControlFlags.IsButton; set { if (value) m_ControlFlags |= ControlFlags.IsButton; else m_ControlFlags &= ~ControlFlags.IsButton; } } internal bool isConfigUpToDate { get => (m_ControlFlags & ControlFlags.ConfigUpToDate) == ControlFlags.ConfigUpToDate; set { if (value) m_ControlFlags |= ControlFlags.ConfigUpToDate; else m_ControlFlags &= ~ControlFlags.ConfigUpToDate; } } internal bool dontReset { get => (m_ControlFlags & ControlFlags.DontReset) == ControlFlags.DontReset; set { if (value) m_ControlFlags |= ControlFlags.DontReset; else m_ControlFlags &= ~ControlFlags.DontReset; } } internal bool usesStateFromOtherControl { get => (m_ControlFlags & ControlFlags.UsesStateFromOtherControl) == ControlFlags.UsesStateFromOtherControl; set { if (value) m_ControlFlags |= ControlFlags.UsesStateFromOtherControl; else m_ControlFlags &= ~ControlFlags.UsesStateFromOtherControl; } } internal bool hasDefaultState => !m_DefaultState.isEmpty; // This method exists only to not slap the internal interaction on all overrides of // FinishSetup(). internal void CallFinishSetupRecursive() { var list = children; for (var i = 0; i < list.Count; ++i) list[i].CallFinishSetupRecursive(); FinishSetup(); } internal string MakeChildPath(string path) { if (this is InputDevice) return path; return $"{this.path}/{path}"; } internal void BakeOffsetIntoStateBlockRecursive(uint offset) { m_StateBlock.byteOffset += offset; var list = children; for (var i = 0; i < list.Count; ++i) list[i].BakeOffsetIntoStateBlockRecursive(offset); } internal int GetDeviceIndex() { var deviceIndex = m_Device.m_DeviceIndex; if (deviceIndex == InputDevice.kInvalidDeviceIndex) throw new InvalidOperationException( $"Cannot query value of control '{path}' before '{device.name}' has been added to system!"); return deviceIndex; } internal bool IsValueConsideredPressed(float value) { if (isButton) return ((ButtonControl)this).IsValueConsideredPressed(value); return value >= ButtonControl.s_GlobalDefaultButtonPressPoint; } internal virtual void AddProcessor(object first) { } #if UNITY_EDITOR internal virtual IEnumerable<object> GetProcessors() { yield return null; } #endif } /// <summary> /// Base class for input controls with a specific value type. /// </summary> /// <typeparam name="TValue">Type of value captured by the control. Note that this does not mean /// that the control has to store data in the given value format. A control that captures float /// values, for example, may be stored in state as byte values instead.</typeparam> public abstract class InputControl<TValue> : InputControl where TValue : struct { public override Type valueType => typeof(TValue); public override int valueSizeInBytes => UnsafeUtility.SizeOf<TValue>(); /// <summary> /// Get the control's current value as read from <see cref="InputControl.currentStatePtr"/> /// </summary> /// <returns>The control's current value.</returns> /// <remarks> /// This can only be called on devices that have been added to the system (<see cref="InputDevice.added"/>). /// </remarks> public TValue ReadValue() { unsafe { return ReadValueFromState(currentStatePtr); } } ////REVIEW: is 'frame' really the best wording here? /// <summary> /// Get the control's value from the previous frame (<see cref="InputControl.previousFrameStatePtr"/>). /// </summary> /// <returns>The control's value in the previous frame.</returns> public TValue ReadValueFromPreviousFrame() { unsafe { return ReadValueFromState(previousFrameStatePtr); } } /// <summary> /// Get the control's default value. /// </summary> /// <returns>The control's default value.</returns> /// <remarks> /// This is not necessarily equivalent to <c>default(TValue)</c>. A control's default value is determined /// by reading its value from the default state (<see cref="InputControl.defaultStatePtr"/>) which in turn /// is determined from settings in the control's registered layout (<see cref="InputControlLayout.ControlItem.defaultState"/>). /// </remarks> public TValue ReadDefaultValue() { unsafe { return ReadValueFromState(defaultStatePtr); } } public unsafe TValue ReadValueFromState(void* statePtr) { if (statePtr == null) throw new ArgumentNullException(nameof(statePtr)); return ProcessValue(ReadUnprocessedValueFromState(statePtr)); } public TValue ReadUnprocessedValue() { unsafe { return ReadUnprocessedValueFromState(currentStatePtr); } } public abstract unsafe TValue ReadUnprocessedValueFromState(void* statePtr); /// <inheritdoc /> public override unsafe object ReadValueFromStateAsObject(void* statePtr) { return ReadValueFromState(statePtr); } /// <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)); var numBytes = UnsafeUtility.SizeOf<TValue>(); if (bufferSize < numBytes) throw new ArgumentException( $"bufferSize={bufferSize} < sizeof(TValue)={numBytes}", nameof(bufferSize)); var value = ReadValueFromState(statePtr); var valuePtr = UnsafeUtility.AddressOf(ref value); UnsafeUtility.MemCpy(bufferPtr, valuePtr, numBytes); } public override unsafe void WriteValueFromBufferIntoState(void* bufferPtr, int bufferSize, void* statePtr) { if (bufferPtr == null) throw new ArgumentNullException(nameof(bufferPtr)); if (statePtr == null) throw new ArgumentNullException(nameof(statePtr)); var numBytes = UnsafeUtility.SizeOf<TValue>(); if (bufferSize < numBytes) throw new ArgumentException( $"bufferSize={bufferSize} < sizeof(TValue)={numBytes}", nameof(bufferSize)); // C# won't let us use a pointer to a generically defined type. Work // around this by using UnsafeUtility. var value = default(TValue); var valuePtr = UnsafeUtility.AddressOf(ref value); UnsafeUtility.MemCpy(valuePtr, bufferPtr, numBytes); WriteValueIntoState(value, statePtr); } /// <inheritdoc /> public override unsafe void WriteValueFromObjectIntoState(object value, void* statePtr) { if (statePtr == null) throw new ArgumentNullException(nameof(statePtr)); if (value == null) throw new ArgumentNullException(nameof(value)); // If value is not of expected type, try to convert. if (!(value is TValue)) value = Convert.ChangeType(value, typeof(TValue)); var valueOfType = (TValue)value; WriteValueIntoState(valueOfType, statePtr); } public virtual unsafe void WriteValueIntoState(TValue value, void* statePtr) { ////REVIEW: should we be able to even tell from layouts which controls support writing and which don't? throw new NotSupportedException( $"Control '{this}' does not support writing"); } /// <inheritdoc /> public override unsafe object ReadValueFromBufferAsObject(void* buffer, int bufferSize) { if (buffer == null) throw new ArgumentNullException(nameof(buffer)); var valueSize = UnsafeUtility.SizeOf<TValue>(); if (bufferSize < valueSize) throw new ArgumentException( $"Expecting buffer of at least {valueSize} bytes for value of type {typeof(TValue).Name} but got buffer of only {bufferSize} bytes instead", nameof(bufferSize)); var value = default(TValue); var valuePtr = UnsafeUtility.AddressOf(ref value); UnsafeUtility.MemCpy(valuePtr, buffer, valueSize); return value; } public override unsafe bool CompareValue(void* firstStatePtr, void* secondStatePtr) { ////REVIEW: should we first compare state here? if there's no change in state, there can be no change in value and we can skip the rest var firstValue = ReadValueFromState(firstStatePtr); var secondValue = ReadValueFromState(secondStatePtr); var firstValuePtr = UnsafeUtility.AddressOf(ref firstValue); var secondValuePtr = UnsafeUtility.AddressOf(ref secondValue); // NOTE: We're comparing raw memory of processed values here (which are guaranteed to be structs or // primitives), not state. Means we don't have to take bits into account here. return UnsafeUtility.MemCmp(firstValuePtr, secondValuePtr, UnsafeUtility.SizeOf<TValue>()) != 0; } public TValue ProcessValue(TValue value) { if (m_ProcessorStack.length > 0) { value = m_ProcessorStack.firstValue.Process(value, this); if (m_ProcessorStack.additionalValues != null) for (var i = 0; i < m_ProcessorStack.length - 1; ++i) value = m_ProcessorStack.additionalValues[i].Process(value, this); } return value; } internal InlinedArray<InputProcessor<TValue>> m_ProcessorStack; // Only layouts are allowed to modify the processor stack. internal TProcessor TryGetProcessor<TProcessor>() where TProcessor : InputProcessor<TValue> { if (m_ProcessorStack.length > 0) { if (m_ProcessorStack.firstValue is TProcessor processor) return processor; if (m_ProcessorStack.additionalValues != null) for (var i = 0; i < m_ProcessorStack.length - 1; ++i) if (m_ProcessorStack.additionalValues[i] is TProcessor result) return result; } return default; } internal override void AddProcessor(object processor) { if (!(processor is InputProcessor<TValue> processorOfType)) throw new ArgumentException( $"Cannot add processor of type '{processor.GetType().Name}' to control of type '{GetType().Name}'", nameof(processor)); m_ProcessorStack.Append(processorOfType); } #if UNITY_EDITOR internal override IEnumerable<object> GetProcessors() { foreach (var processor in m_ProcessorStack) yield return processor; } #endif internal InputProcessor<TValue>[] processors => m_ProcessorStack.ToArray(); } }