using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.InputSystem.Layouts;
namespace UnityEngine.InputSystem
{
///
/// A typed and named source of input values in a hierarchy of controls.
///
///
/// Controls can have children which in turn may have children. At the root of the child
/// hierarchy is always an (which themselves are InputControls).
///
/// Controls can be looked up by their (see ).
///
/// Each control must have a unique within the of
/// its . Multiple names can be assigned to controls using aliases (see
/// ). Name lookup is case-insensitive.
///
/// For display purposes, a control may have a separate . 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 .
/// This is the case for the "Left Button" on the , for example, which is
/// commonly abbreviated "LMB".
///
/// In addition to names, a control may have usages associated with it (see ).
/// 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 for a list of standardized usages.
///
/// Controls do not actually store values. Instead, every control receives an
/// 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 ,
/// for example, can be stored as a float or as a byte or in a number of other formats.
/// 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 ). 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 . 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
/// . Most types of controls are derived from
/// which has APIs specific to the type of value of the control (e.g. .
///
/// The following example demonstrates various common operations performed on input controls:
///
///
///
/// // 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();
///
///
///
///
///
///
[DebuggerDisplay("{DebuggerDisplay(),nq}")]
public abstract class InputControl
{
///
/// The name of the control, i.e. the final name part in its path.
///
///
/// 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 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.
/// See , , and .
///
public string name => m_Name;
////TODO: protect against empty strings
///
/// The text to display as the name of the control.
///
///
/// 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 ) 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 . However, specific
/// controls may override this behavior. , 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".
///
/// There is a short name version, see .
///
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;
}
///
/// An alternate, abbreviated (for example "LMB" instead of "Left Button").
///
///
/// If the control has no abbreviated version, this will be null. Note that this behavior is different
/// from which will fall back to 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. See .
///
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;
}
///
/// Full path all the way from the root.
///
///
/// This will always be the "effective" path of the control, i.e. it will not contain
/// elements such as usages ("{Back}") 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 (/).
///
/// Allocates on first hit. Paths are not created until someone asks for them.
///
/// For more details on the path see .
///
///
/// Example: "/gamepad/leftStick/x"
///
public string path
{
get
{
if (m_Path == null)
m_Path = InputControlPath.Combine(m_Parent, m_Name);
return m_Path;
}
}
///
/// Layout name for the control it is based on.
///
///
/// This is the layout name rather than a reference to an 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.
///
public string layout => m_Layout;
///
/// Semicolon-separated list of variants of the control layout or "default".
///
///
/// "Lefty" when using the "Lefty" gamepad layout.
///
public string variants => m_Variants;
///
/// The device that this control is a part of.
///
///
/// This is the root of the control hierarchy. For the device at the root, this
/// will point to itself (See ).
///
public InputDevice device => m_Device;
///
/// The immediate parent of the control.
///
///
/// 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).
/// See the related field.
///
public InputControl parent => m_Parent;
///
/// List of immediate child controls below this.
///
///
/// Does not allocate.
/// See the related field.
///
public ReadOnlyArray children =>
new ReadOnlyArray(m_Device.m_ChildrenForEachControl, m_ChildStartIndex, m_ChildCount);
///
/// List of usage tags associated with the control.
///
///
/// 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 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.
///
/// 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 . Controls, on the other hand,
/// can currently only be assigned usages through layouts (
/// or ).
///
/// can be used to add a device.
/// can be used to remove a device.
///
///
///
/// // Bind to any control which is tagged with the "Back" usage on any device.
/// var backAction = new InputAction(binding: "*/{Back}");
///
///
public ReadOnlyArray usages =>
new ReadOnlyArray(m_Device.m_UsagesForEachControl, m_UsageStartIndex, m_UsageCount);
///
/// List of alternate names for the control.
///
///
/// List of aliased alternate names for the control.
/// An example of an alias would be 'North' for the 'Triangle' button on a Playstation pad (or 'Y' button on Xbox pad).
///
public ReadOnlyArray aliases =>
new ReadOnlyArray(m_Device.m_AliasesForEachControl, m_AliasStartIndex, m_AliasCount);
///
/// Information about where the control stores its state, such as format, offset and size.
///
public InputStateBlock stateBlock => m_StateBlock;
///
/// Retrieves whether the control is considered [noisy](xref:input-system-controls#noisy-controls).
///
/// True if the control produces noisy input.
///
/// A control is considered "noisy" if it produces different values without necessarily requiring user
/// interaction. For example, XR head mounted displays
/// or sensors such as a . For more information, refer to
/// [Noisy controls](xref:input-system-controls#noisy-controls).
///
/// The value of this property is determined by the layout that the
/// control has been built from (using ).
///
/// > [!NOTE]
/// > For devices, this property is true if any control on the device
/// is marked as noisy.
///
/// The primary effect of being noisy is on and
/// on interactive rebinding (see ).
/// However, being noisy also affects automatic resetting of controls that happens when the application
/// loses focus. For more information, refer to [Noisy controls](xref:input-system-controls#noisy-controls).
///
///
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;
}
}
///
/// Retrieves whether the control is considered [synthetic](xref:input-system-controls#synthetic-controls).
///
/// True if the control does not represent an actual physical control on the device.
///
/// A control is considered "synthetic" if it does not correspond to an actual, physical control on the
/// device. For example, or the up/down/left/right buttons added
/// by . For more information, refer to
/// [Synthetic controls](xref:input-system-controls#synthetic-controls).
///
/// The value of this property is determined by the layout that the
/// control has been built from (using ).
///
/// The primary effect of being synthetic is on interactive rebinding (see
/// ) where the input system favors
/// non-synthetic controls over synthetic ones for rebinding. For more information, refer to
/// [Synthetic controls](xref:input-system-controls#synthetic-controls).
///
///
public bool synthetic
{
get => (m_ControlFlags & ControlFlags.IsSynthetic) != 0;
internal set
{
if (value)
m_ControlFlags |= ControlFlags.IsSynthetic;
else
m_ControlFlags &= ~ControlFlags.IsSynthetic;
}
}
///
/// Fetch a control from the control's hierarchy by name.
///
/// A control path. See .
///
/// Note that matching is case-insensitive.
/// (see ).
/// An alternative method is .
///
///
///
/// gamepad["leftStick"] // Returns Gamepad.leftStick
/// gamepad["leftStick/x"] // Returns Gamepad.leftStick.x
/// gamepad["{PrimaryAction}"] // Returns the control with PrimaryAction usage, that is, Gamepad.aButton
///
///
/// cannot be found.
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;
}
}
///
/// Returns the underlying value type of this control.
///
/// Type of values produced by the control.
///
/// 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 with .
/// The size can be determined with .
///
public abstract Type valueType { get; }
///
/// Size in bytes of values that the control returns.
///
///
/// The type can be determined with .
///
public abstract int valueSizeInBytes { get; }
///
/// Compute an absolute, normalized magnitude value that indicates the extent to which the control
/// is actuated. Shortcut for .
///
///
/// Amount of actuation of the control or -1 if it cannot be determined.
///
public float magnitude => EvaluateMagnitude();
///
/// Return a string representation of the control.
///
///
/// Return a string representation of the control. Useful for debugging.
///
/// A string representation of the control.
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
///
/// Compute an absolute, normalized magnitude value that indicates the extent to which the control
/// is actuated.
///
/// Amount of actuation of the control or -1 if it cannot be determined.
///
/// Magnitudes do not make sense for all types of controls. For example, for a control that represents
/// an enumeration of values (such as ), 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
/// has a higher or lower "magnitude" as a state of ).
///
/// Controls that have no meaningful magnitude will return -1 when calling this method. Any negative
/// return value should be considered an invalid value.
///
///
public unsafe float EvaluateMagnitude()
{
return EvaluateMagnitude(currentStatePtr);
}
///
/// Compute an absolute, normalized magnitude value that indicates the extent to which the control
/// is actuated in the given state.
///
///
/// Magnitudes do not make sense for all types of controls. For example, for a control that represents
/// an enumeration of values (such as ), 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
/// has a higher or lower "magnitude" as a state of ).
///
/// Controls that have no meaningful magnitude will return -1 when calling this method. Any negative
/// return value should be considered an invalid value.
///
/// State containing the control's .
/// Amount of actuation of the control or -1 if it cannot be determined.
///
public virtual unsafe float EvaluateMagnitude(void* statePtr)
{
return -1;
}
///
/// Read the control's final, processed value from the given buffer and return the value as an object.
///
/// Buffer to read the value from.
/// Size of in bytes, which must be large enough to store the value.
/// The control's value as stored in .
///
/// Read the control's final, processed value from the given buffer and return the value as an object.
///
/// This method allocates GC memory and should not be used during normal gameplay operation.
///
/// is null.
/// is smaller than the size of the value to be read.
///
public abstract unsafe object ReadValueFromBufferAsObject(void* buffer, int bufferSize);
///
/// Read the control's final, processed value from the given state and return the value as an object.
///
/// State to read the value for the control from.
/// The control's value as stored in .
///
/// Read the control's final, processed value from the given state and return the value as an object.
///
/// This method allocates GC memory and should not be used during normal gameplay operation.
///
/// is null.
///
public abstract unsafe object ReadValueFromStateAsObject(void* statePtr);
///
/// Read the control's final, processed value from the given state and store it in the given buffer.
///
/// State to read the value for the control from.
/// Buffer to store the value in.
/// Size of in bytes. Must be at least .
/// If it is smaller, will be thrown.
/// is null, or is null.
/// is smaller than .
///
///
public abstract unsafe void ReadValueFromStateIntoBuffer(void* statePtr, void* bufferPtr, int bufferSize);
///
/// Read a value from the given memory and store it as state.
///
/// Memory containing value, to store into the state.
/// Size of in bytes. Must be at least .
/// State containing the control's . Will receive the state
/// as converted from the given value.
///
/// 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.
///
/// The control does not support writing. This is the case, for
/// example, that compute values (such as the magnitude of a vector).
///
///
public virtual unsafe void WriteValueFromBufferIntoState(void* bufferPtr, int bufferSize, void* statePtr)
{
throw new NotSupportedException(
$"Control '{this}' does not support writing");
}
///
/// Read a value object and store it as state in the given memory.
///
/// Value for the control to store in the state.
/// State containing the control's . Will receive
/// the state as converted from the given value.
///
/// 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.
///
/// The control does not support writing. This is the case, for
/// example, that compute values (such as the magnitude of a vector).
///
public virtual unsafe void WriteValueFromObjectIntoState(object value, void* statePtr)
{
throw new NotSupportedException(
$"Control '{this}' does not support writing");
}
///
/// Compare the value of the control as read from to that read from
/// and return true if they are equal.
///
/// Memory containing the control's .
/// Memory containing the control's
/// True if the value of the control is equal in both and
/// .
///
/// Unlike , 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 .
///
/// This method will apply epsilons () when comparing floats.
///
///
public abstract unsafe bool CompareValue(void* firstStatePtr, void* secondStatePtr);
///
/// Try to find a child control matching the given path.
///
/// A control path. See .
/// The first direct or indirect child control that matches the given
/// or null if no control was found to match.
/// is null or empty.
///
/// Note that if the given path matches multiple child controls, only the first control
/// encountered in the search will be returned.
///
/// This method is equivalent to calling .
///
///
///
/// // 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");
///
///
public InputControl TryGetChildControl(string path)
{
if (string.IsNullOrEmpty(path))
throw new ArgumentNullException(nameof(path));
return InputControlPath.TryFindChild(this, path);
}
///
/// Try to find a child control matching the given path.
///
/// A control path. See .
/// The type of control to locate.
/// The first direct or indirect child control that matches the given
/// or null if no control was found to match.
/// is null or empty.
/// No control found with the specified type.
///
/// Note that if the given path matches multiple child controls, only the first control
/// encountered in the search will be returned.
///
public TControl TryGetChildControl(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;
}
///
/// Find a child control matching the given path.
///
/// A control path. See .
/// The first direct or indirect child control that matches the given
/// or null if no control was found to match.
/// is null or empty.
/// No control found with the specified type.
/// The control cannot be found.
///
/// Note that if the given path matches multiple child controls, only the first control
/// encountered in the search will be returned.
///
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;
}
///
/// Find a child control matching the given path.
///
/// A control path. See .
/// The type of control to locate.
/// The first direct or indirect child control that matches the given
/// or null if no control was found to match.
/// is null or empty.
/// No control found with the specified type.
/// The control cannot be found.
///
/// Note that if the given path matches multiple child controls, only the first control
/// encountered in the search will be returned.
///
public TControl GetChildControl(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;
}
///
/// Constructor for the InputControl
///
///
/// Constructor for the InputControl
///
protected InputControl()
{
// Set defaults for state block setup. Subclasses may override.
m_StateBlock.byteOffset = InputStateBlock.AutomaticOffset; // Request automatic layout by default.
}
///
/// Perform final initialization tasks after the control hierarchy has been put into place.
///
///
/// 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.
///
///
///
/// 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");
/// }
/// }
///
///
protected virtual void FinishSetup()
{
}
///
/// Call if the configuration has in the interim been invalidated
/// by a .
///
///
/// 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 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
/// .
///
///
///
/// using UnityEngine.InputSystem;
/// using UnityEngine.InputSystem.Utilities;
///
/// // 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");
/// }
///
///
///
protected void RefreshConfigurationIfNeeded()
{
if (!isConfigUpToDate)
{
RefreshConfiguration();
isConfigUpToDate = true;
}
}
///
/// Refresh the configuration of the control. This is used to update the control's state (e.g. Keyboard Layout or display Name of Keys).
///
///
/// The system will call this method automatically whenever a change is made to one of the control's configuration properties.
/// 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 which is fetching display names for individual keys
/// from the underlying platform).
/// See .
///
///
///
/// using UnityEngine.InputSystem;
/// using UnityEngine.InputSystem.Utilities;
///
/// public class MyDevice : InputDevice
/// {
/// public enum Orientation
/// {
/// Horizontal,
/// Vertical,
/// }
/// private Orientation m_Orientation;
/// private static InternedString s_Vertical = new InternedString("Vertical");
/// private static InternedString s_Horizontal = new InternedString("Horizontal");
///
/// public Orientation orientation
/// {
/// get
/// {
/// // Call RefreshOrientation if the configuration of the device has been
/// // invalidated since last time we initialized m_Orientation.
/// // Calling RefreshConfigurationIfNeeded() is sufficient in most cases, RefreshConfiguration() forces the refresh.
/// RefreshConfiguration();
/// return m_Orientation;
/// }
/// }
/// protected override void RefreshConfiguration()
/// {
/// // Set Orientation back to horizontal. Alternatively fetch from device.
/// m_Orientation = Orientation.Horizontal;
/// // 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;
/// }
/// }
/// }
///
///
protected virtual void RefreshConfiguration()
{
}
////TODO: drop protected access
///
/// Information about a memory region storing input state.
///
protected internal InputStateBlock m_StateBlock;
////REVIEW: shouldn't these sit on the device?
///
/// The state data buffer for the device.
///
///
/// The state data buffer for the device.
///
protected internal unsafe void* currentStatePtr => InputStateBuffers.GetFrontBufferForDevice(GetDeviceIndex());
///
/// The state data buffer for the device from the previous frame.
///
///
/// The state data buffer for the device from the previous frame.
///
protected internal unsafe void* previousFrameStatePtr => InputStateBuffers.GetBackBufferForDevice(GetDeviceIndex());
///
/// The default state data buffer
///
///
/// Buffer that has state for each device initialized with default values.
///
protected internal unsafe void* defaultStatePtr => InputStateBuffers.s_DefaultStateBuffer;
///
/// Return the memory that holds the noise mask for the control.
///
/// Noise bit mask for the control.
///
/// Like with all state blocks, the specific memory block for the control is found at the memory
/// region specified by .
///
/// 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.
///
/// A control can be marked as .
///
protected internal unsafe void* noiseMaskPtr => InputStateBuffers.s_NoiseMaskBuffer;
///
/// The offset of this control's state relative to its device root.
///
///
/// 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 the controls on the device. This property always returns
/// the "unbaked" offset.
///
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;
// Value caching
// These values will be set to true during state updates if the control has actually changed value.
// Set to true initially so default state will be returned on the first call
internal bool m_CachedValueIsStale = true;
internal bool m_UnprocessedCachedValueIsStale = true;
////REVIEW: store these in arrays in InputDevice instead?
internal PrimitiveValue m_DefaultState;
internal PrimitiveValue m_MinValue;
internal PrimitiveValue m_MaxValue;
internal FourCC m_OptimizedControlDataType;
///
/// The type of the state memory associated with the control.
///
///
/// For some types of control you can safely read/write state memory directly
/// which is much faster than calling ReadUnprocessedValueFromState/WriteValueIntoState.
/// This method returns a type that you can use for reading/writing the control directly,
/// or it returns if it's not possible for this type of control.
///
/// For example, AxisControl might be a "float" in state memory, and if no processing is applied during reading (e.g. no invert/scale/etc),
/// then you could read it as float in memory directly without calling ReadUnprocessedValueFromState, which is faster.
/// Additionally, if you have a Vector3Control which uses 3 AxisControls as consecutive floats in memory,
/// you can cast the Vector3Control state memory directly to Vector3 without calling ReadUnprocessedValueFromState on x/y/z axes.
///
/// The value returned for any given control is computed automatically by the Input System, when the control's setup configuration changes.
/// There are some parameter changes which don't trigger a configuration change (such as the clamp, invert, normalize, and scale parameters on AxisControl),
/// so if you modify these, the optimized data type is not automatically updated. In this situation, you should manually update it by calling .
///
public FourCC optimizedControlDataType => m_OptimizedControlDataType;
///
/// Calculates and returns an optimized data type that can represent a control's value in memory directly.
///
///
/// The value then is cached in .
/// This method is for internal use only, you should not call this from your own code.
///
///
/// An optimized data type that can represent a control's value in memory directly.
///
protected virtual FourCC CalculateOptimizedControlDataType()
{
return InputStateBlock.kFormatInvalid;
}
///
/// Apply built-in parameters changes.
///
///
/// Apply built-in parameters changes (e.g. , others).
/// Recompute for impacted controls and clear cached value
///
///
///
/// Gamepad.all[0].leftTrigger.WriteValueIntoState(0.5f, Gamepad.all[0].currentStatePtr);
/// Gamepad.all[0].ApplyParameterChanges();
///
///
public void ApplyParameterChanges()
{
// First we go through all children of our own hierarchy
SetOptimizedControlDataTypeRecursively();
// Then we go through all parents up to the root, because our own change might influence their optimization status
// e.g. let's say we have a tree where root is Vector3 and children are three AxisControl
// And user is calling this method on AxisControl which goes from Float to NotOptimized.
// Then we need to also transition Vector3 to NotOptimized as well.
var currentParent = parent;
while (currentParent != null)
{
currentParent.SetOptimizedControlDataType();
currentParent = currentParent.parent;
}
// Also use this method to mark cached values as stale
MarkAsStaleRecursively();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetOptimizedControlDataType()
{
// setting check need to be inline so we clear optimizations if setting is disabled after the fact
m_OptimizedControlDataType = InputSystem.s_Manager.optimizedControlsFeatureEnabled
? CalculateOptimizedControlDataType()
: (FourCC)InputStateBlock.kFormatInvalid;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void SetOptimizedControlDataTypeRecursively()
{
// Need to go depth-first because CalculateOptimizedControlDataType might depend on computed values of children
if (m_ChildCount > 0)
{
foreach (var inputControl in children)
inputControl.SetOptimizedControlDataTypeRecursively();
}
SetOptimizedControlDataType();
}
// This function exists to warn users to start using ApplyParameterChanges for edge cases that were previously not intentionally supported,
// where control properties suddenly change underneath us without us anticipating that.
// This is mainly to AxisControl fields being public and capable of changing at any time even if we were not anticipated such a usage pattern.
// Also it's not clear if InputControl.stateBlock.format can potentially change at any time, likely not.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
// Only do this check in and editor in hope that it will be sufficient to catch any misuse during development.
// It is not done in debug builds because it has a performance cost and it will show up when profiled.
[Conditional("UNITY_EDITOR")]
internal void EnsureOptimizationTypeHasNotChanged()
{
if (!InputSystem.s_Manager.optimizedControlsFeatureEnabled)
return;
var currentOptimizedControlDataType = CalculateOptimizedControlDataType();
if (currentOptimizedControlDataType != optimizedControlDataType)
{
Debug.LogError(
$"Control '{name}' / '{path}' suddenly changed optimization state due to either format " +
$"change or control parameters change (was '{optimizedControlDataType}' but became '{currentOptimizedControlDataType}'), " +
"this hinders control hot path optimization, please call control.ApplyParameterChanges() " +
"after the changes to the control to fix this error.");
// Automatically fix the issue
// Note this function is only executed in the editor
m_OptimizedControlDataType = currentOptimizedControlDataType;
}
if (m_ChildCount > 0)
{
foreach (var inputControl in children)
inputControl.EnsureOptimizationTypeHasNotChanged();
}
}
[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();
SetOptimizedControlDataTypeRecursively();
}
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)
{
}
internal void MarkAsStale()
{
m_CachedValueIsStale = true;
m_UnprocessedCachedValueIsStale = true;
}
internal void MarkAsStaleRecursively()
{
MarkAsStale();
foreach (var inputControl in children)
{
inputControl.MarkAsStale();
if (inputControl is ButtonControl buttonControl)
{
// If everything is becoming stale, update all press states so we can reevaluate
buttonControl.UpdateWasPressed();
#if UNITY_EDITOR
buttonControl.UpdateWasPressedEditor();
#endif
}
}
}
#if UNITY_EDITOR
internal virtual IEnumerable