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 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 { /// /// 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}")] [Scripting.Preserve] 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. /// /// /// /// /// 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". /// /// 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, /// 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 \u2190" and not just "\u2190". Note that if a parent /// control has no short name, its long name will be used instead. /// /// 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. /// /// /// Example: "/gamepad/leftStick/x" /// /// /// public string path { get { if (m_Path == null) m_Path = InputControlPath.Combine(m_Parent, m_Name); return m_Path; } } /// /// Layout the control 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. /// /// public InputDevice device => m_Device; /// /// 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). /// /// public InputControl parent => m_Parent; /// /// List of immediate children. /// /// /// Does not allocate. /// /// 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. /// /// /// /// // Bind to any control which is tagged with the "Back" usage on any device. /// var backAction = new InputAction(binding: "*/{Back}"); /// /// /// /// 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 ). /// /// /// /// /// /// /// public ReadOnlyArray usages => new ReadOnlyArray(m_Device.m_UsagesForEachControl, m_UsageStartIndex, m_UsageCount); // List of alternate names for the control. public ReadOnlyArray aliases => new ReadOnlyArray(m_Device.m_AliasesForEachControl, m_AliasStartIndex, m_AliasCount); // Information about where the control stores its state. public InputStateBlock stateBlock => m_StateBlock; /// /// Whether the control is considered noisy. /// /// True if the control produces noisy input. /// /// A control is considered "noisy" if it produces different values without necessarily requiring user /// interaction. A good example are sensors (see ). 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 () that the /// control has been built from. /// /// Note that for devices () this property is true if any control on the device /// is marked as noisy. /// /// The primary effect of being noise is on and /// on interactive rebinding (see ). /// 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 Application.runInBackground /// is true and the device the control belongs to is marked as ), /// 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. /// /// /// 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) list[i].noisy = true; } else m_ControlFlags &= ~ControlFlags.IsNoisy; } } /// /// Whether the control is considered synthetic. /// /// 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. An example for this is or the up/down/left/right buttons added /// by . /// /// The value of this property is determined by the layout () that the /// control has been built from. /// /// The primary effect of being synthetic is in interactive rebinding (see /// ) where non-synthetic /// controls will be favored over synthetic ones. This means, for example, that if both /// "<Gamepad>/leftStick/x" and "<Gamepad>/leftStick/left" are /// suitable picks, "<Gamepad>/leftStick/x" will be favored as it represents /// input from an actual physical control whereas "<Gamepad>/leftStick/left" /// represents input from a made-up control. If, however, the "left" button is the only /// viable pick, it will be accepted. /// /// /// 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. /// /// /// Note that path matching is case-insensitive. /// /// /// /// gamepad["leftStick"] // Returns Gamepad.leftStick /// gamepad["leftStick/x"] // Returns Gamepad.leftStick.x /// gamepad["{PrimaryAction}"] // Returns the control with PrimaryAction usage, i.e. 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. /// /// /// public abstract Type valueType { get; } /// /// Size in bytes of values that the control returns. /// /// public abstract int valueSizeInBytes { get; } /// /// 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(); } } /// /// 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. /// /// 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; } 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. /// /// /// The control's value as stored in . /// /// 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. /// 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. /// State containing the control's . Will receive /// the state 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. /// /// /// /// // 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"); /// /// /// /// This method is equivalent to calling . /// public InputControl TryGetChildControl(string path) { if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); return InputControlPath.TryFindChild(this, path); } 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; } 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(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. } /// /// 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 /// . /// /// /// /// // 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; } } protected virtual void RefreshConfiguration() { } protected internal InputStateBlock m_StateBlock; ////REVIEW: shouldn't these sit on the device? protected internal unsafe void* currentStatePtr => InputStateBuffers.GetFrontBufferForDevice(ResolveDeviceIndex()); protected internal unsafe void* previousFrameStatePtr => InputStateBuffers.GetBackBufferForDevice(ResolveDeviceIndex()); 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. /// /// 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 of 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; ////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, } internal bool isConfigUpToDate { get => (m_ControlFlags & ControlFlags.ConfigUpToDate) == ControlFlags.ConfigUpToDate; set { if (value) m_ControlFlags |= ControlFlags.ConfigUpToDate; else m_ControlFlags &= ~ControlFlags.ConfigUpToDate; } } 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 ResolveDeviceIndex() { 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 virtual void AddProcessor(object first) { } } /// /// Base class for input controls with a specific value type. /// /// 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. [Scripting.Preserve] public abstract class InputControl : InputControl where TValue : struct { public override Type valueType => typeof(TValue); public override int valueSizeInBytes => UnsafeUtility.SizeOf(); /// /// Get the control's current value as read from /// /// The control's current value. /// /// This can only be called on devices that have been added to the system (). /// public TValue ReadValue() { unsafe { return ReadValueFromState(currentStatePtr); } } ////REVIEW: is 'frame' really the best wording here? /// /// Get the control's value from the previous frame (). /// /// The control's value in the previous frame. public TValue ReadValueFromPreviousFrame() { unsafe { return ReadValueFromState(previousFrameStatePtr); } } /// /// Get the control's default value. /// /// The control's default value. /// /// This is not necessarily equivalent to default(TValue). A control's default value is determined /// by reading its value from the default state () which in turn /// is determined from settings in the control's registered layout (). /// 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); /// public override unsafe object ReadValueFromStateAsObject(void* statePtr) { return ReadValueFromState(statePtr); } /// 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(); 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(); 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); } /// 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"); } /// public override unsafe object ReadValueFromBufferAsObject(void* buffer, int bufferSize) { if (buffer == null) throw new ArgumentNullException(nameof(buffer)); var valueSize = UnsafeUtility.SizeOf(); 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()) != 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> m_ProcessorStack; // Only layouts are allowed to modify the processor stack. internal TProcessor TryGetProcessor() where TProcessor : InputProcessor { 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 processorOfType)) throw new ArgumentException( $"Cannot add processor of type '{processor.GetType().Name}' to control of type '{GetType().Name}'", nameof(processor)); m_ProcessorStack.Append(processorOfType); } internal InputProcessor[] processors => m_ProcessorStack.ToArray(); } }