From 0d0acda9919edb4e4ac94ecff2d91a5f324bc03d Mon Sep 17 00:00:00 2001 From: dom Date: Fri, 24 Jan 2025 15:06:45 -0500 Subject: [PATCH 01/25] Add initial axis input proposal draft --- .../Proposal - Axis Input Devices.md | 624 ++++++++++++++++++ 1 file changed, 624 insertions(+) create mode 100644 documentation/proposals/Proposal - Axis Input Devices.md diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md new file mode 100644 index 0000000000..3027757413 --- /dev/null +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -0,0 +1,624 @@ +# Summary + +"Everything is an axis" - the idea that every input device can be abstracted into a series of axes (floating point values from 0-1), and an API for defining and working with such axes in a generic way. + +# Contributors + +- Dom Portera (@domportera) +- Dylan Perks (@Perksey) + +# Current Status + +- [x] Proposed +- [ ] Discussed with Community +- [ ] Approved +- [ ] Implemented + +# Dependencies +This proposal assumes some knowledge of the [Multi-Backend Input proposal](./Proposal - Multi-Backend Input.md) and the previous version of this proposal. + +# Background + +This proposal is born of a conversation regarding the [Multi-Backend input proposal](./Proposal - Multi-Backend +Input.md), where the topic of "Every input is an axis" was brought up. This proposal is an attempt to flesh out that idea into a proper API and lay the foundation for how this could enable more advanced features in the future. + +# Goals + +The hopes is to put a small level of burden on backend implementations, thoughtfully implementing this API, we will: + +- Make working with inputs more modular and consistent from both a high and low level +- Allow for both higher-level and lower-level common code and functionality + - such as shared logic handling buttons, keys, accelerometers, gyroscopes, touchpads, etc +- Allow for creative uses of backend input systems +- Allow for the creation of simple or "free" input accessibility features +- Provide device-specific input descriptions without the need for the user to know what device or backend is in use +- Provide a foundation for future device-level key/button mapping +- Provide a foundation for to enable the creation of straightforward or automatic virtual device creation systems +- Enable the creation of extremely modular input systems, including the creation of virtual input devices, whereby an implementation could exist that allows for a straightforward, re-mappable implementation of device emulation + - to be further fleshed out in a future proposal. +- Provide an API foundation for in-depth input accessibility features (related to the above-mentioned modularity) + +# Note on Requirements + +This proposal can be thought of in two primary areas: the API surface, and the implementation requirements. Wherever it was possible, the API surface was designed to enforce certain requirements, however there are quite a few requirements that must be followed that are not enforced by the API itself. Comments have been added to the API where these requirements are necessary and further detail is provided where needed. Feedback on clarity is welcome. + +# Core API + +The root of the API is in the interface `IAxisDevice`. This interface is essentially a host for collections of axes paired with definitions of these axes and how they relate to one another. In order to be used, each `IAxisDevice` must be validated, similar to Vulkan's validation layers, through the use of the `AxisDeviceRegistry` class. + +It is important to note that the value of an input axis a single 32-bit floating point value. is primarily expected between 0-1.0. There are some axis types, or rather `AxisTrait`s, that are not straightforwardly normalized, and those respective axes can be labeled as such (more on that later). + +## IAxisDevice + +```csharp +/// +/// This represents a single input device, physical or virtual, that is comprised of a collection of axes (or floats) +/// The IReadOnlyList interface is used to provide the normalized values of each axis, if it is indeed normalized (most should be). Otherwise, it would provide the raw, unnormalized value. +/// +public interface IAxisDevice : IReadOnlyList, IInputDevice +{ + /// + /// The main entry-point for useful information about common axis groups - e.g. joysticks, trackpads, buttons, gyros, etc. + /// Turns single axes/floats into more broadly useable information + /// Must be provided in order of + /// + IReadOnlyList Groups { get; } + + /// + /// The description of each axis in the hardware device + /// This information is used primarily to allow for programmatically defined axis groups, axis devices, runtime validation, + /// and custom device mappings and handling. + /// Must be provided in order of + /// + IReadOnlyList Axes { get; } + + /// + /// Provides raw values for each axis as defined by the device's native API + /// Must be provided in order of + /// + IReadOnlyList RawValues { get; } + + /// + /// A human-readable description of the device, intended for end-user display + /// + public string? Description { get; } + + /// + /// A human-readable description of the device's status, intended for end-user display + /// In the case of device malfunctions, especially those that are composed of multiple devices, + /// this can be used to display any issues preventing the device from functioning properly + /// + public string? Status { get; } + + /// + /// False if this is a single real-world device, true if this is a device based on one or more physical devices, + /// or is entirely virtual (e.g. a pointer in a VR environment, a virtual gamepad, etc) + /// + /// Note: defining "virtual" input devices should not *necessarily* come with + /// the burden of a full-blown IAxisDevice implementation. Future proposal may include a request to move this to IInputDevice, which would help make the IInputDevice <-> IAxisDevice APIs more consistent with each other. + /// + bool IsVirtual { get; } +} +``` + +## IAxisInputHandler + +This is how a developer could subscribe directly to the axis changes of an `IAxisDevice`. It mimicks the `IGamepadInputHandler` interface from the [Multi-Backend Input proposal](./Proposal - Multi-Backend Input.md). + +```csharp + +public interface IAxisInputHandler +{ + public void OnAxisChanged(IAxisDevice device, AxisDescription description, float value); +} +``` + +## Axis Descriptions + +There are two primary structures that give meaning to the axes of an `IAxisDevice`: `AxisDescription` and `AxisGroup`. The former defines the axes themselves, and the latter defines how these axes relate to one another. Note that both of these structures have an `Index` field, which must match the index of the `IAxisDevice`'s collection that it is defined in. + +### Struct Definition + +```csharp +/// +/// This describes a single axis on a device +/// +/// the index of the axis in the device +/// the traints of the axis, described above +/// A human-readable name for the axis +/// The bounds of the raw value - a value of 'default' indicates it is typical (0, 1), or +/// in the case that the provided traits are , this must be default and indicates no known bounds. +public readonly record struct AxisDescription( + int Index, + AxisTrait Traits, + string Name, + Vector2 RawValueBounds = default) +{ + public static implicit operator int(AxisDescription description) => description.Index; +} +``` + +### Axis Traits +These are the primary flags through which an axis's use is defined. Its definition includes several constraints meant to both make defining axes less cumbersome and enforce logically consistent behaviour where possible. This enum is one of the primary places where validation needs to occur at runtime, and some flags place implementation requirements on the implementer. Wherever possible, we should seek to provide default implementations of expected behaviours regarding these constraints. + +```csharp + +/// +/// This is how individual axes are defined +/// Pay attention to the flags which require specific implementation details related to their RawValues and normalization +/// +[Flags] +public enum AxisTrait +{ + Unknown = 0, + Analog = 1 << 0, + Binary = 1 << 1, + + // an analog axis that explicitly does not return to center or zero like a joystick or trigger + // things like touch-pads, mice, or world-space positions + Point = 1 << 8 | Analog | HasRawValue, + + // ---- Physical orientation ---- + Orientation = 1 << 2 | Analog, + Rotation = 1 << 3 | Orientation, + + // this is specified because single axes of euler angle components can be useful by themselves, + // whereas anything shorter than 3 or 4 axes is not for quaternion rotation components + /// + /// Indicates that this axis is a component of a euler angle (do we want this to be in degrees or radians?) + /// If this flag is set, the axis is expected to be normalized between 0-360 degrees, with wrapping applied + /// It is required you do NOT set , as it will be assumed to be (0, 360/2pi). + /// + EulerAngleComponent = 1 << 4 | Rotation | HasRawValue, + + /// + /// Acceleration - in meters per second
+ /// If this is set, it is expected that the axis is NOT provided any raw value constraints, + /// as it is not expected to be normalized + ///
+ Acceleration = 1 << 6 | Orientation | RawValueOnly, + + /// + /// Position - raw values are in meters if world-space, in pixels if screen-space, etc
+ /// Normalized values are required if the bounds are known via the device's API. + /// If they are unknown, this must be paired with + ///
+ Position = 1 << 7 | Orientation | Point, + // ----------------------------- + + /// + /// Indicates that the raw value provided for this axis is not the same as the normalized value.
+ /// If this flag is signaled on an axis, it is highly recommended that the bounds are provided as well, even if they are infinite + /// This can be useful for things like euler angle components, where single axes 0-360 degrees (or 0-2pi radians? do we want to standardize which?) can be useful by themselves + ///
+ HasRawValue = 1 << 24 | Analog, + + /// + /// Indicates that there is no one-size-fits-all way to normalize this axis, and must be handled in a case-by-case basis
+ /// Examples of this would be:
+ /// - Accelerometers, as the relevant magnitude differs depending on the application
+ /// - VR controller position without a bounding area
+ /// - Quaternion rotation values, as these can be expected to be negative
+ ///
+ /// If this is set, it is expected that the axis is NOT provided any raw value constraints + ///
+ RawValueOnly = 1 << 25 | HasRawValue, + + /// + /// Things like a mouse movement, trackpad, etc + /// + Delta = 1 << 26 | HasRawValue, + + /// + /// Indicates that there is no one-size-fits-all way to normalize this axis, and must be handled in a case-by-case basis
+ /// Examples of this would be:
+ /// - Mouse position
+ /// - Trackpad without an API for "absolute" position + ///
+ DeltaOnly = 1 << 27 | Delta | RawValueOnly, + + /// + /// Used for axes that are on the left side of a device from the user's perspective. + /// Useful for left/right handedness accommodation and various symmetrical devices.

+ /// For example: a left stick on a gamepad, a left trigger, a left shoulder button, the DPad on a gamepad, etc + ///
+ LeftSide = 1 << 30, + + /// + /// Used for axes that are on the right side of a device from the user's perspective. + /// Useful for left/right handedness accommodation and various symmetrical devices.

+ /// For example: a right stick on a gamepad, a right trigger, a right shoulder button, the face buttons on a gamepad, etc + ///
+ RightSide = 1 << 31, +} +``` + +### Example Definition List + +The following is a demonstration of how this `AxisDescription` list could be populated by an input backend: + +```csharp +public class DualsenseDevice : IAxisDevice +{ + IReadOnlyList + private static readonly IReadOnlyList Axes = + [ + // left analog stick + new(0, AxisTrait.Analog | AxisTrait.LeftSide, "Left Joystick X-"), + new(1, AxisTrait.Analog | AxisTrait.LeftSide, "Left Joystick X+"), + new(2, AxisTrait.Analog | AxisTrait.LeftSide, "Left Joystick Y-"), + new(3, AxisTrait.Analog | AxisTrait.LeftSide, "Left Joystick Y+"), + new(4, AxisTrait.Binary | AxisTrait.LeftSide, "Left Joystick Press"), + new(5, AxisTrait.Binary | AxisTrait.LeftSide, "Left Joystick Touch"), + + + // right analog stick + new(6, AxisTrait.Analog | AxisTrait.RightSide, "Right Joystick X-"), + new(7, AxisTrait.Analog | AxisTrait.RightSide, "Right Joystick X+"), + new(8, AxisTrait.Analog | AxisTrait.RightSide, "Right Joystick Y-"), + new(9, AxisTrait.Analog | AxisTrait.RightSide, "Right Joystick Y+"), + new(10, AxisTrait.Binary | AxisTrait.RightSide, "Right Joystick Press"), + new(11, AxisTrait.Binary | AxisTrait.RightSide, "Right Joystick Touch"), + + // d-pad + new(12, AxisTrait.Binary | AxisTrait.LeftSide, "D-Pad Left"), + new(13, AxisTrait.Binary | AxisTrait.LeftSide, "D-Pad Right"), + new(14, AxisTrait.Binary | AxisTrait.LeftSide, "D-Pad Down"), + new(15, AxisTrait.Binary | AxisTrait.LeftSide, "D-Pad Up"), + + // face buttons + new(16, AxisTrait.Binary | AxisTrait.RightSide, "Square"), + new(17, AxisTrait.Binary | AxisTrait.RightSide, "Circle"), + new(18, AxisTrait.Binary | AxisTrait.RightSide, "Cross"), + new(19, AxisTrait.Binary | AxisTrait.RightSide, "Triangle"), + + // touchpad + new(20, AxisTrait.Binary, "Touchpad Press"), + + // touchpad touch points - up to 4 touch points + new(21, AxisTrait.Position, "Touchpad X1"), + new(22, AxisTrait.Position, "Touchpad Y1"), + new(23, AxisTrait.Position, "Touchpad X2"), + new(24, AxisTrait.Position, "Touchpad Y2"), + new(25, AxisTrait.Position, "Touchpad X3"), + new(26, AxisTrait.Position, "Touchpad Y3"), + new(27, AxisTrait.Position, "Touchpad X4"), + new(28, AxisTrait.Position, "Touchpad Y4"), + + // select/start/home + new(29, AxisTrait.Binary | AxisTrait.LeftSide, "Share"), + new(30, AxisTrait.Binary | AxisTrait.RightSide, "Start"), + new(31, AxisTrait.Binary, "Home"), + + // shoulder buttons + new(32, AxisTrait.Binary | AxisTrait.LeftSide, "L1"), + new(33, AxisTrait.Binary | AxisTrait.RightSide, "R1"), + new(34, AxisTrait.Analog | AxisTrait.LeftSide, "L2"), + new(35, AxisTrait.Analog | AxisTrait.RightSide, "R2"), + + // gyro + new(36, AxisTrait.EulerAngleComponent, "Gyro X"), + new(37, AxisTrait.EulerAngleComponent, "Gyro Y"), + new(38, AxisTrait.EulerAngleComponent, "Gyro Z"), + + // accelerometer + new(39, AxisTrait.Acceleration, "Accelerometer X"), + new(40, AxisTrait.Acceleration, "Accelerometer Y"), + new(41, AxisTrait.Acceleration, "Accelerometer Z"), + + // battery level + new(42, AxisTrait.Analog, "Battery Level"), + + // microphone input level + new(43, AxisTrait.Analog, "Microphone Level"), + ]; +} + ``` + +As additional food for thought, a Sony Dualshock 2 controller whose face buttons are famously (though infrequently used as) analog, might contain face buttons with the following definition: + +```csharp +_ = new AxisDescription(0, AxisTrait.Binary | AxisTrait.Analog | AxisTrait.RightSide, "Square"); +``` + +## Axis Groups +`AxisGroup`s refer to collections of axes whose outputs or layouts are related to one another. If used smartly, they can be used to provide novel high-level remapping schemes, virtual devices, or other features. + +### Struct Definition + +```csharp +/// +/// This is the declaration of a group of axes within a specific device +/// +/// Index within the list of s +/// The intended purpose of this group +/// The indices of the axes that make up this group +/// The AxisGroup index of the "twin" of this group - e.g. if this is the left thumbstick, the twin would be the right thumbstick +/// A human-readable name for the group +/// Can names be inferred at runtime from the AxisDescription and AxisGroup? Should they be nullable/optional +/// in the definition and then populated at runtime if they're missing? +public readonly record struct AxisGroup( + int Index, + AxisGroupType Purpose, + IReadOnlyList Axes, + int? TwinIndex = null, + string Name = "Unknown") +{ + public static implicit operator int(AxisGroup group) => group.Index; +} +``` + +### Axis Group Types +`AxisGroupType` is the primary way in which an `AxisGroup` is given meaning. It is how analog sticks, touchpads, DPads, mouse travel, etc are defined. + +Like `AxisDescription`, it contains hard-coded constraints to help ensure that groups are defined correctly. It also has a few flags that enforce certain requirements on the implementer. We should strive to provide default implementations of expected behaviours regarding these constraints wherever possible. This is another important source of information that must be validated at runtime. + + + +```csharp +/// +/// This is how one defines the purpose of groups of multiple axes in a device +/// It has specifications that must be followed to be considered valid +/// +[Flags] +public enum AxisGroupType : uint +{ + Unknown = 0u, + + // an ordering convention for family axes is left/right/bottom/top/back/forward - "negative" to "positive" values + + // (West, East, South, North) / (Left, Right, Down, Up) / (X, B, A, Y) - xbox / (square, circle, cross, triangle) - playstation + // 4 independent axes using the ordering convention above + FourFaceButtons = 1u << 1, + + // Specified in addition to FourFaceButtons due to the constraints often placed on DPad axes from a hardware level + // 2 Axes, (X, Y) (-1, 1) + // made of 4 physical axes, (X-, X+, Y-, Y+) + DPad = 1u << 2 | FourFaceButtons, + + /// + /// 1D joystick, 1 logical axis, (-1, 1) + /// made of 2 physical axes, (X-, X+) + /// More physically resembling a "lever" than a joystick + /// + Joystick1D = 1u << 3 | DPad, + + /// + /// 2D joystick, 2 logical axes, (X, Y) (-1, 1) + /// made of 4 physical axes, (X-, X+, Y-, Y+) + /// If constructed with 5 axes, the 5th axis is the "pressure" axis for a pointer + /// More abstractly, this is a Position2D that returns to 0,0 when released + /// + Joystick2D = 1u << 4 | Position2D | DPad, + + /// + /// 3D joystick + /// 3 logical Axes (X, Y, Z), (-1, 1) + /// made of 6 physical axes, (X-, X+, Y-, Y+, Z-, Z+) + /// If constructed with 7 axes, the 7th axis is the "pressure" axis for a pointer + /// Can be used for analog stick with a press-in button, or a 3D mouse, etc + /// More abstractly, this is a Position3D that returns to 0,0,0 when released + /// + Joystick3D = 1u << 5 | Position3D, + + /// + /// 1D touch surface or tracker + /// 1 logical axis, (-1, 1) + /// made of 2 physical axes, (-X, +X) + /// Physically, this can be a represented as a touch strip + /// + Position1D = 1u << 6, + + /// + /// 2D touch surface or tracker + /// 2 logical axes, (X, Y) (-1, 1) + /// made of 4 physical axes (-X, +X, -Y, +Y) + /// If constructed with 5 axes, the 5th axis is the "pressure" axis for a pointer + /// + Position2D = 1u << 7, + + /// + /// 3D touch surface or tracker, 3 logical axes, (X, Y, Z) + /// made of 6 physical axes (-X, +X, -Y, +Y, -Z, +Z) + /// If constructed with 7 axes, the 7th axis is the "pressure" axis for a pointer + /// + Position3D = 1u << 8, + + // should this be 6 and 8 axes, similar to how a 2d joystick is defined by 4 axes? + RotationEuler = 1u << 9, // gyroscope, gyroscope + magnetomete, VR peripheral rotation - requires 3 axes, XYZ order + + RotationQuaternion = + 1u << 10, // gyroscope, gyroscope + magnetomete, VR peripheral rotation - requires 4 axes, XYZW order + + Accelerometer = 1u << 11, // 3D accelerometer, requires 3 axes, XYZ order + + DeviceInformation = 1u << 24, // battery level, microphone level, etc + LeftHanded = 1u << 30, // allowing for left/right swap of symmetrical devices and labeling left/right buttons + RightHanded = 1u << 31, // allowing for left/right swap of symmetrical devices +} +``` + +### Axis Group Directional Convention +This pertains to `AxisGroupType`s that suggest positional input - i.e. `FourFaceButtons`, `DPad`, `Joystick1D`, `Joystick2D`, `Joystick3D`, `Position1D`, `Position2D`, `Position3D`, `RotationEuler`, `RotationQuaternion`, and `Accelerometer`. The convention is as follows: + +Note that `AxisGroup` sets a standard for dealing with multi-dimensional axes, which dictates the order in which `AxisGroup.Axes` is populated by `AxisDescription` indices. The convention to be followed by all comparable axis groups is as follows: + +- Axes are intended to be ordered in the following way: + - Typical 3D axis notation is to be followed (X, Y, Z), with Y being the vertical axis + - Within a single "physical" axis (i.e.. X, Y, etc) "Negative" values come before "positive" values. Or, in other words, "Left" before "Right", "Down" before "Up", "Back" before "Forward". + - Irregular controllers might be up to interpretation, but the convention should be followed as closely as possible, primarily with respect to the actual physical device, rather than convention. + + +The following is a chart laying out example orderings for different axis groups. + +| Hardware inputs | Axis ordering for `AxisGroup` _______________________________________________________________________________________ | +|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ![DPad buttons](https://upload.wikimedia.org/wikipedia/commons/4/4d/D-Pad_of_an_Xbox_One_controller.jpg) | (Left, Right, Down, Up) | +| ![Analog stick](https://upload.wikimedia.org/wikipedia/commons/a/a9/GameCube_Analog_Stick.jpg) | (Left, Right, Down, Up)
(X-, X+, Y-, Y+) | +| ![gamecube face buttons](https://upload.wikimedia.org/wikipedia/commons/d/da/Gamecube_controller_face_buttons.jpg) | (B, X, A, Y) | +| ![playstation face buttons](https://upload.wikimedia.org/wikipedia/commons/thumb/c/c4/PlayStation_original_controller_face_buttons.jpg/684px-PlayStation_original_controller_face_buttons.jpg) | (Square, Circle, Cross, Triangle) | +| ![N64 face buttons](https://upload.wikimedia.org/wikipedia/commons/4/48/Nintendo_64_controller_face_buttons.jpg) | Primary - (C Left, C Right, C Down, C Up)

Secondary - (B, C down, A, C left) | +| ![WASD keys](https://upload.wikimedia.org/wikipedia/commons/0/05/Cursor_keys--WASD.svg) | (A, D, S, W)
Arrow keys would be (left, right, down, up of course)
(J, L, K, I), etc | +| ![Fight stick](https://upload.wikimedia.org/wikipedia/commons/8/8f/Wii_Arcade_Stick.png?20200918233300) | The "face" buttons are not a candidate for `AxisGroupType.FourFaceButtons` designation due to their layout, so fight sticks and some other retro controllers would likely exclude their face buttons from any particular axis group

The joystick would of course be (-X Left, +X Right, -Y Down, +Y Up) | +| ![Accelerometer](https://cdn.phidgets.com/docs/images/9/96/Accelerometer_Intro.jpg) | (X, Y, Z) | +| ![Gyroscope](https://upload.wikimedia.org/wikipedia/commons/b/b8/Roll_Pitch_Yaw.JPG) | Euler representation: (yaw, pitch, roll)

Quaternion representation: (X, Y, Z, W) | + +As you may have gathered, a single axis can be assigned multiple axis groups. This is perfectly legal and *encouraged*. However, the ordering of such groups can become significant to users of the device. + +In general, the larger order of axis groups is not significant except for the following: + +- If a specific axis is a member of more than one axis group, they are assumed to be ordered in order of primacy for that particular axis. + - Note that this ordering is *relative* - they do not need to be defined in consecutive order. +- If two or more `AxisGroup`s share the precise same `AxisGroupType` (all flags included), then some implementations are fair to assume they are in order of their primacy as well. + +Though it is possible (and often recommended) to use `AxisGroupType` as flags, including every supported `AxisGroupType` associated with a set of axes, doing so is not required. It may be advantageous to utilize the above ordering constraint in order to define a preference for a particular set of axes to be used as a specific `AxisGroupType`. For example, defining a D-Pad on a controller that could be defined as: + +```csharp +new(3, AxisGroupType.DPad | AxisGroupType.FourFaceButtons | AxisGroupType.LeftHanded, [14, 15, 16, 17], 6, "D-Pad"), +``` + +could instead be defined as: +```csharp +new(3, AxisGroupType.DPad | AxisGroupType.LeftHanded, [14, 15, 16, 17], 6, "D-Pad"), +new(4, AxisGroupType.FourFaceButtons | AxisGroupType.LeftHanded, [14, 15, 16, 17], 6, "D-Pad"), +``` +As previously stated, the latter grants you the benefit of having a defined order of primacy for the axes in the D-Pad group, which can be useful for certain implementations. + + +Though no such API is defined in this proposal, I strongly encourage the development of extension methods to aid in determining the "ranking" of `AxisGroup`s for any given axis, as well as determining the "default" set of axis groups that make up a given device. (Request for feedback: would the latter require an additional property in `IAxisDevice`?) + +### Example Axis Group List + +Below is an incomplete example of how an `AxisGroup` list can be populated. + +```csharp +private static readonly IReadOnlyList Families = +[ + new(0, AxisGroupType.Joystick2D | AxisGroupType.LeftHanded, [0, 1, 2, 3], 1, "Left Joystick"), + new(1, AxisGroupType.Joystick2D | AxisGroupType.RightHanded, [4, 5, 6, 7], 0, "Right Joystick"), + new(3, AxisGroupType.DPad | AxisGroupType.FourFaceButtons | AxisGroupType.LeftHanded, [14, 15, 16, 17], 6, "D-Pad"), + new(4, AxisGroupType.FourFaceButtons | AxisGroupType.RightHanded, [30, 31, 32, 33], 5, "Face Buttons"), + new(5, AxisGroupType.Position2D, [18, 19], null, "Touchpad"), + new(6, AxisGroupType.Unknown, [20, 21], null, "Unknown") +]; +``` + + +# Deadzones + +With this proposal, I thought it pertinent to define a base "Deadzone" API. Using this, we can create specific implementations to turn `AxisTrait.Analog` axes into a binary button signal, or of course implement traditional joystick-related deadzones and handle other noisy inputs + +## The Struct + +```csharp + +/// +/// Represents a deadzone for an axis +/// ExpectedNoise can be ignored by deadzone implementations, but is relevant for those involving +/// logic-oriented debouncing +/// +public record struct Deadzone +{ + public Vector2 Zone; + public float ExpectedNoise; + + /// + /// If true, the deadzone will be skipped entirely, and values within the deadzone will be treated as 0. + /// Values just outside the deadzone will be treated as their literal value. + /// + /// More commonly, if false, IDeadzoneHandlers will treat the outer values as a full normalizable range. + /// For example, if an axis has a deadzone of (0.25, 0.75), + /// then 0.0 - 0.25 would be treated as 0.0 - 0.5, and 0.75 - 1.0 would be treated as 0.5 - 1.0 + /// + public bool SkipDeadValues; + + public Deadzone(Vector2 zone, float expectedNoise, bool skipDeadValues = false) + { + Zone = zone; + ExpectedNoise = expectedNoise; + SkipDeadValues = skipDeadValues; + } + + public Deadzone(float minimum, float expectedNoise, bool skipDeadValues = false) + { + Zone = new Vector2(minimum, 1); + ExpectedNoise = expectedNoise; + SkipDeadValues = skipDeadValues; + } + + public Deadzone(float minimum, bool skipDeadValues = false) + { + Zone = new Vector2(minimum, 1); + ExpectedNoise = 0; + SkipDeadValues = skipDeadValues; + } +} +``` + +## The Handlers + +```csharp +/// +/// An interface for handling deadzones in different ways +/// +public interface IDeadzoneHandler +{ +public float GetValue(IAxisDevice device, int descriptionIndex, Deadzone deadzone); +public bool IsInDeadzone(IAxisDevice device, int descriptionIndex, Deadzone deadzone); + + public bool IsGroupInDeadzone(IAxisDevice device, int groupIndex, Deadzone deadzone); + + // Requires that the length of the deadzones span matches the number of axes in the group + public bool IsGroupInDeadzone(IAxisDevice device, int groupIndex, ReadOnlySpan deadzones); +} +``` + +# Device Registration +Due to the complex set of constraints detailed above, it is important that a validation layer exists to ensure that higher-level code can trust the device is following important conventions and constraints. For each device that connects, it must first register to the `AxisDeviceRegistry` in order to be used as an `IAxisDevice`. + +Any interested party can then reference the `AxisDeviceRegistry` to retrieve all available devices. Notably, this is missing a way to alert users of devices that have been newly connected or disconnected. Feedback is requested on this front: is this necessary or should it be implemented elsewhere? + +```csharp +public class AxisDeviceRegistry +{ + /// + /// Required before usage of an IAxisDevice - this is essentially a "validation layer" where we can check if the device + /// configuration is valid, e.g. "do this AxisGroup's axes have definitions that satisfy the axis group?" + /// Things like checking for axes with a rotational definition, Left/Right handedness, groups' axis counts, etc + /// Will populate the proper AxisDeviceRegistry collections using runtime type checks + /// Registration is required to use a device each time it connects + /// + /// Should we develop a way to register these conditions at compile time as like a list of functions? + /// + public bool TryRegister(IAxisDevice device, [NotNullWhen(false)] out string? error); + + /// + /// Unregisters a device + /// Will remove the device from the registry, required to be called when a device disconnects + /// + /// + /// True if the device was previously registered and is now unregistered + public bool Unregister(IAxisDevice device); + + /// + /// All currently registered devices + /// + public IReadOnlyList Devices { get; } + + /// + /// Runtime-generated virtual devices + /// + public IReadOnlyList VirtualDevices { get; } + + /// + /// Physical devices + /// + public IReadOnlyList PhysicalDevices { get; } + + // A future composability proposal can include additional properties to retrieve all available axis groups, irrespective + // of the source devices + + // there may also be a case for making the above 3 collection types (All, Virtual, Physical) a model that can be followed by + // the standard input context, which would be the primary benefit to adding `IsVirtual` to `IInputDevice` - to be discussed in a future proposal +} +``` From 5c43ee51b67604c50995b93e2a7acd2972260d33 Mon Sep 17 00:00:00 2001 From: dom Date: Sat, 25 Jan 2025 21:05:45 -0500 Subject: [PATCH 02/25] Update documentation/proposals/Proposal - Axis Input Devices.md Co-authored-by: Dylan Perks <11160611+Perksey@users.noreply.github.com> --- documentation/proposals/Proposal - Axis Input Devices.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index 3027757413..97e9d6d068 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -361,7 +361,8 @@ Like `AxisDescription`, it contains hard-coded constraints to help ensure that g /// It has specifications that must be followed to be considered valid /// [Flags] -public enum AxisGroupType : uint +public enum AxisGroupType : ulong + { Unknown = 0u, From ca31414b4553ad2478e83b17cf7f1885b166e0f4 Mon Sep 17 00:00:00 2001 From: dom Date: Sat, 25 Jan 2025 21:06:56 -0500 Subject: [PATCH 03/25] Update documentation/proposals/Proposal - Axis Input Devices.md Co-authored-by: Dylan Perks <11160611+Perksey@users.noreply.github.com> --- documentation/proposals/Proposal - Axis Input Devices.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index 97e9d6d068..f6318b1268 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -148,7 +148,7 @@ These are the primary flags through which an axis's use is defined. Its definiti /// Pay attention to the flags which require specific implementation details related to their RawValues and normalization /// [Flags] -public enum AxisTrait +public enum AxisTrait : ulong { Unknown = 0, Analog = 1 << 0, From b7911c43b3ad6fdf4ac8d74daca68d2394ae32a4 Mon Sep 17 00:00:00 2001 From: dom Date: Sat, 25 Jan 2025 21:18:35 -0500 Subject: [PATCH 04/25] further document `AxisTrait` --- documentation/proposals/Proposal - Axis Input Devices.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index f6318b1268..3ed865b341 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -150,8 +150,16 @@ These are the primary flags through which an axis's use is defined. Its definiti [Flags] public enum AxisTrait : ulong { + // To be treated as a default value / null. No axis should be left with this trait, and if they are it will be treated as an error. Unknown = 0, + + // Indicates that the axis is non-binary and has multiple values aside from 0 and 1. + // Note: inputs with a finite set of values that aren't binary should either be marked as an Analog axis, or more likely be split into several Binary axes. Analog = 1 << 0, + + // This axis has two possible values - on/off, pressed/not pressed, 0 or 1. Implementations claiming a "Binary" axis trait will be expected to satisfy the condition that + // The `Deadzone` will determine the default on/off behavior, which will default to `if(value > 0.0) return "on"` + // The same default behavior will apply to any axis being treated as a Binary input, whether or not it has this trait. Binary = 1 << 1, // an analog axis that explicitly does not return to center or zero like a joystick or trigger From 7bc0746a90051fa57762d33dcb00c0dc2dc5efd1 Mon Sep 17 00:00:00 2001 From: dom Date: Sat, 25 Jan 2025 21:34:35 -0500 Subject: [PATCH 05/25] Add additional comment --- documentation/proposals/Proposal - Axis Input Devices.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index 3ed865b341..358de78133 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -150,7 +150,7 @@ These are the primary flags through which an axis's use is defined. Its definiti [Flags] public enum AxisTrait : ulong { - // To be treated as a default value / null. No axis should be left with this trait, and if they are it will be treated as an error. + // To be treated as a default value / null. No axis should be left with this trait, and if they are it will be treated as an error or ignored. Unknown = 0, // Indicates that the axis is non-binary and has multiple values aside from 0 and 1. @@ -170,13 +170,14 @@ public enum AxisTrait : ulong Orientation = 1 << 2 | Analog, Rotation = 1 << 3 | Orientation, - // this is specified because single axes of euler angle components can be useful by themselves, - // whereas anything shorter than 3 or 4 axes is not for quaternion rotation components /// /// Indicates that this axis is a component of a euler angle (do we want this to be in degrees or radians?) /// If this flag is set, the axis is expected to be normalized between 0-360 degrees, with wrapping applied /// It is required you do NOT set , as it will be assumed to be (0, 360/2pi). /// + + // this is specified because single axes of euler angle components can be useful by themselves, + // whereas anything shorter than 3 or 4 axes is not useful for quaternion rotation components EulerAngleComponent = 1 << 4 | Rotation | HasRawValue, /// @@ -372,6 +373,7 @@ Like `AxisDescription`, it contains hard-coded constraints to help ensure that g public enum AxisGroupType : ulong { + // To be treated as a default value / null. No axis should be left with this trait, and if they are it will be treated as an error or ignored. Unknown = 0u, // an ordering convention for family axes is left/right/bottom/top/back/forward - "negative" to "positive" values From eada457ad9c15533062b77a8741257204382bd13 Mon Sep 17 00:00:00 2001 From: dom Date: Sat, 25 Jan 2025 21:35:39 -0500 Subject: [PATCH 06/25] improved phrasing for extension method suggestion Co-authored-by: Dylan Perks <11160611+Perksey@users.noreply.github.com> --- documentation/proposals/Proposal - Axis Input Devices.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index 358de78133..86d1b2e976 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -496,7 +496,8 @@ new(4, AxisGroupType.FourFaceButtons | AxisGroupType.LeftHanded, [14, 15, 16, 17 As previously stated, the latter grants you the benefit of having a defined order of primacy for the axes in the D-Pad group, which can be useful for certain implementations. -Though no such API is defined in this proposal, I strongly encourage the development of extension methods to aid in determining the "ranking" of `AxisGroup`s for any given axis, as well as determining the "default" set of axis groups that make up a given device. (Request for feedback: would the latter require an additional property in `IAxisDevice`?) +Though no such API is defined in this proposal, the Silk.NET team reserves the right to expose extension methods to aid in determining the "ranking" of `AxisGroup`s for any given axis, as well as determining the "default" set of axis groups that make up a given device. (Request for feedback: would the latter require an additional property in `IAxisDevice`?) + ### Example Axis Group List From b4f9c299713d86fc26b0b871d5a0718149600bf8 Mon Sep 17 00:00:00 2001 From: dom Date: Sat, 25 Jan 2025 21:48:05 -0500 Subject: [PATCH 07/25] Move description, status, and IsVirtual properties into IInputDevice --- .../Proposal - Axis Input Devices.md | 53 +++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index 86d1b2e976..74697dddb6 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -48,6 +48,38 @@ The root of the API is in the interface `IAxisDevice`. This interface is essenti It is important to note that the value of an input axis a single 32-bit floating point value. is primarily expected between 0-1.0. There are some axis types, or rather `AxisTrait`s, that are not straightforwardly normalized, and those respective axes can be labeled as such (more on that later). +## IInputDevice +We propose the following properties be added to `IInputDevice` for their general utility and to help keep this API and the [base input API](./Proposal - Multi-Backend +Input.md) consistent with each other. + +```csharp +public interface IInputDevice : IEquatable +{ + // ---- Current API ----- // + nint Id { get; } + string Name { get; } + // ---------------------- // + + /// + /// A human-readable description of the device, intended for end-user display + /// + public string? Description => null; + + /// + /// A human-readable description of the device's status, intended for end-user display + /// In the case of device malfunctions, especially those that are composed of multiple devices, + /// this can be used to display any issues preventing the device from functioning properly + /// + public string? Status => null; + + /// + /// False if this is a single real-world device, true if this is a device based on one or more physical devices, + /// or is entirely virtual (e.g. a pointer in a VR environment, a virtual gamepad, etc) + /// + bool IsVirtual => false; +} +``` + ## IAxisDevice ```csharp @@ -77,27 +109,6 @@ public interface IAxisDevice : IReadOnlyList, IInputDevice /// Must be provided in order of /// IReadOnlyList RawValues { get; } - - /// - /// A human-readable description of the device, intended for end-user display - /// - public string? Description { get; } - - /// - /// A human-readable description of the device's status, intended for end-user display - /// In the case of device malfunctions, especially those that are composed of multiple devices, - /// this can be used to display any issues preventing the device from functioning properly - /// - public string? Status { get; } - - /// - /// False if this is a single real-world device, true if this is a device based on one or more physical devices, - /// or is entirely virtual (e.g. a pointer in a VR environment, a virtual gamepad, etc) - /// - /// Note: defining "virtual" input devices should not *necessarily* come with - /// the burden of a full-blown IAxisDevice implementation. Future proposal may include a request to move this to IInputDevice, which would help make the IInputDevice <-> IAxisDevice APIs more consistent with each other. - /// - bool IsVirtual { get; } } ``` From e74d8b0758fad636d3cc58c0ab8425b12374b3d4 Mon Sep 17 00:00:00 2001 From: dom Date: Sat, 25 Jan 2025 22:10:42 -0500 Subject: [PATCH 08/25] Add feature to AxisGroupType.Position1D and request for feedback to AxisGroupType.Position2D and 3D --- documentation/proposals/Proposal - Axis Input Devices.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index 74697dddb6..d4134c597b 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -426,7 +426,8 @@ public enum AxisGroupType : ulong /// /// 1D touch surface or tracker /// 1 logical axis, (-1, 1) - /// made of 2 physical axes, (-X, +X) + /// made of 2 physical axes, (-X, +X). + /// If constructed with 3 axes, the 3rd axis is the "pressure" axis for a pointer /// Physically, this can be a represented as a touch strip /// Position1D = 1u << 6, @@ -436,6 +437,8 @@ public enum AxisGroupType : ulong /// 2 logical axes, (X, Y) (-1, 1) /// made of 4 physical axes (-X, +X, -Y, +Y) /// If constructed with 5 axes, the 5th axis is the "pressure" axis for a pointer + /// * Request for feedback: do we want to allow this to be defined with 2 "physical" axes as well? + /// Wherein if it is provided with 3 "physical" axes, the 3rd is the "pressure" axis /// Position2D = 1u << 7, @@ -443,6 +446,8 @@ public enum AxisGroupType : ulong /// 3D touch surface or tracker, 3 logical axes, (X, Y, Z) /// made of 6 physical axes (-X, +X, -Y, +Y, -Z, +Z) /// If constructed with 7 axes, the 7th axis is the "pressure" axis for a pointer + /// * Request for feedback: do we want to allow this to be defined with 3 "physical" axes as well? + /// Wherein if it is provided with 4 "physical" axes, the 4th is the "pressure" axis /// Position3D = 1u << 8, From 85b6caab7ad12bb13201104b6d0913e654a2b82f Mon Sep 17 00:00:00 2001 From: dom Date: Sat, 25 Jan 2025 22:26:11 -0500 Subject: [PATCH 09/25] Remove request for feedback this feedback was more-or-less answered by myself in the axis group priority section. if we need or would like to provide anything more specific, it could be an extension method --- documentation/proposals/Proposal - Axis Input Devices.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index d4134c597b..5f0f5e2450 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -512,7 +512,7 @@ new(4, AxisGroupType.FourFaceButtons | AxisGroupType.LeftHanded, [14, 15, 16, 17 As previously stated, the latter grants you the benefit of having a defined order of primacy for the axes in the D-Pad group, which can be useful for certain implementations. -Though no such API is defined in this proposal, the Silk.NET team reserves the right to expose extension methods to aid in determining the "ranking" of `AxisGroup`s for any given axis, as well as determining the "default" set of axis groups that make up a given device. (Request for feedback: would the latter require an additional property in `IAxisDevice`?) +Though no such API is defined in this proposal, the Silk.NET team reserves the right to expose extension methods to aid in determining the "ranking" of `AxisGroup`s for any given axis, as well as determining the "default" set of axis groups that make up a given device. ### Example Axis Group List From 07ce99e09c717d8e1bd99d5d243010b84ed56bf7 Mon Sep 17 00:00:00 2001 From: dom Date: Sun, 26 Jan 2025 09:30:33 -0500 Subject: [PATCH 10/25] mark AxisDescription params as `in` --- documentation/proposals/Proposal - Axis Input Devices.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index 5f0f5e2450..8d185bf2a8 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -120,7 +120,7 @@ This is how a developer could subscribe directly to the axis changes of an `IAxi public interface IAxisInputHandler { - public void OnAxisChanged(IAxisDevice device, AxisDescription description, float value); + public void OnAxisChanged(IAxisDevice device, in AxisDescription description, float value); } ``` @@ -145,7 +145,7 @@ public readonly record struct AxisDescription( string Name, Vector2 RawValueBounds = default) { - public static implicit operator int(AxisDescription description) => description.Index; + public static implicit operator int(in AxisDescription description) => description.Index; } ``` From ccff3b5dd07c82624533b18ba4f89b139b59a506 Mon Sep 17 00:00:00 2001 From: dom Date: Sun, 26 Jan 2025 09:54:33 -0500 Subject: [PATCH 11/25] Push validation of IAxisDevice to the InputContext --- .../Proposal - Axis Input Devices.md | 75 +++++-------------- 1 file changed, 17 insertions(+), 58 deletions(-) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index 8d185bf2a8..12fd126bbc 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -39,16 +39,9 @@ The hopes is to put a small level of burden on backend implementations, thoughtf - Provide an API foundation for in-depth input accessibility features (related to the above-mentioned modularity) # Note on Requirements - This proposal can be thought of in two primary areas: the API surface, and the implementation requirements. Wherever it was possible, the API surface was designed to enforce certain requirements, however there are quite a few requirements that must be followed that are not enforced by the API itself. Comments have been added to the API where these requirements are necessary and further detail is provided where needed. Feedback on clarity is welcome. -# Core API - -The root of the API is in the interface `IAxisDevice`. This interface is essentially a host for collections of axes paired with definitions of these axes and how they relate to one another. In order to be used, each `IAxisDevice` must be validated, similar to Vulkan's validation layers, through the use of the `AxisDeviceRegistry` class. - -It is important to note that the value of an input axis a single 32-bit floating point value. is primarily expected between 0-1.0. There are some axis types, or rather `AxisTrait`s, that are not straightforwardly normalized, and those respective axes can be labeled as such (more on that later). - -## IInputDevice +# Modifications to the Multi-Backend Input Proposal We propose the following properties be added to `IInputDevice` for their general utility and to help keep this API and the [base input API](./Proposal - Multi-Backend Input.md) consistent with each other. @@ -80,6 +73,22 @@ public interface IInputDevice : IEquatable } ``` +# Core API + +The root of the API is in the interface `IAxisDevice`. This interface is essentially a host for collections of axes paired with definitions of these axes and how they relate to one another. + +It is important to note that the value of an input axis a single 32-bit floating point value. is primarily expected between 0-1.0. There are some axis types, or rather `AxisTrait`s, that are not straightforwardly normalized, and those respective axes can be labeled as such (more on that later). + +## Device Validation +Due to the complex set of constraints detailed below, it is important that a validation layer exists to ensure that higher-level code can trust the device is following important conventions and constraints. As a result, each `IAxisDevice` must be validated, similar to Vulkan's validation layers, through the current `IInputContext` if such a context desires to make reliable use of this API. The Silk.NET team shall make their validation implementation easily callable by custom `IInputContext` implementations due to its inevitable complexity. + +The signature of Silk's validation method should resemble the following: + +```csharp +public static bool IsAxisDeviceValid(IAxisDevice device, [NotNullWhen(false)] out string? error); +``` + + ## IAxisDevice ```csharp @@ -600,53 +609,3 @@ public bool IsInDeadzone(IAxisDevice device, int descriptionIndex, Deadzone dead public bool IsGroupInDeadzone(IAxisDevice device, int groupIndex, ReadOnlySpan deadzones); } ``` - -# Device Registration -Due to the complex set of constraints detailed above, it is important that a validation layer exists to ensure that higher-level code can trust the device is following important conventions and constraints. For each device that connects, it must first register to the `AxisDeviceRegistry` in order to be used as an `IAxisDevice`. - -Any interested party can then reference the `AxisDeviceRegistry` to retrieve all available devices. Notably, this is missing a way to alert users of devices that have been newly connected or disconnected. Feedback is requested on this front: is this necessary or should it be implemented elsewhere? - -```csharp -public class AxisDeviceRegistry -{ - /// - /// Required before usage of an IAxisDevice - this is essentially a "validation layer" where we can check if the device - /// configuration is valid, e.g. "do this AxisGroup's axes have definitions that satisfy the axis group?" - /// Things like checking for axes with a rotational definition, Left/Right handedness, groups' axis counts, etc - /// Will populate the proper AxisDeviceRegistry collections using runtime type checks - /// Registration is required to use a device each time it connects - /// - /// Should we develop a way to register these conditions at compile time as like a list of functions? - /// - public bool TryRegister(IAxisDevice device, [NotNullWhen(false)] out string? error); - - /// - /// Unregisters a device - /// Will remove the device from the registry, required to be called when a device disconnects - /// - /// - /// True if the device was previously registered and is now unregistered - public bool Unregister(IAxisDevice device); - - /// - /// All currently registered devices - /// - public IReadOnlyList Devices { get; } - - /// - /// Runtime-generated virtual devices - /// - public IReadOnlyList VirtualDevices { get; } - - /// - /// Physical devices - /// - public IReadOnlyList PhysicalDevices { get; } - - // A future composability proposal can include additional properties to retrieve all available axis groups, irrespective - // of the source devices - - // there may also be a case for making the above 3 collection types (All, Virtual, Physical) a model that can be followed by - // the standard input context, which would be the primary benefit to adding `IsVirtual` to `IInputDevice` - to be discussed in a future proposal -} -``` From b627f8b48d5f41b22b7d50391ae26027b66b860a Mon Sep 17 00:00:00 2001 From: dom Date: Sun, 26 Jan 2025 10:19:05 -0500 Subject: [PATCH 12/25] Update input context validation description ty perksey :) Co-authored-by: Dylan Perks <11160611+Perksey@users.noreply.github.com> --- documentation/proposals/Proposal - Axis Input Devices.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index 12fd126bbc..ecd37c4c9d 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -80,9 +80,9 @@ The root of the API is in the interface `IAxisDevice`. This interface is essenti It is important to note that the value of an input axis a single 32-bit floating point value. is primarily expected between 0-1.0. There are some axis types, or rather `AxisTrait`s, that are not straightforwardly normalized, and those respective axes can be labeled as such (more on that later). ## Device Validation -Due to the complex set of constraints detailed below, it is important that a validation layer exists to ensure that higher-level code can trust the device is following important conventions and constraints. As a result, each `IAxisDevice` must be validated, similar to Vulkan's validation layers, through the current `IInputContext` if such a context desires to make reliable use of this API. The Silk.NET team shall make their validation implementation easily callable by custom `IInputContext` implementations due to its inevitable complexity. +Due to the complex set of constraints detailed below, it is important that a validation layer exists to ensure that higher-level code can trust the device is following important conventions and constraints. As a result, each `IAxisDevice` must be validated, similar to Vulkan's validation layers, before `InputContext` can use the device to create a wrapper/implementation of the higher-level interfaces defined in Multi-Backend Input (or otherwise make use of the raw axes). This validation is done by `InputContext` if used, however if the user is using their own device aggregator type then it is important that the validation implementation is easily callable by those implementations due to its inevitable complexity. -The signature of Silk's validation method should resemble the following: +This validation shall be exposed as follows: ```csharp public static bool IsAxisDeviceValid(IAxisDevice device, [NotNullWhen(false)] out string? error); From 8bcdd7effec7d19c755eb00d2dc2c81e3f6a5304 Mon Sep 17 00:00:00 2001 From: dom Date: Sun, 26 Jan 2025 10:20:59 -0500 Subject: [PATCH 13/25] update validation method declaration --- documentation/proposals/Proposal - Axis Input Devices.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index ecd37c4c9d..4fdc5b1013 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -85,7 +85,11 @@ Due to the complex set of constraints detailed below, it is important that a val This validation shall be exposed as follows: ```csharp -public static bool IsAxisDeviceValid(IAxisDevice device, [NotNullWhen(false)] out string? error); +namespace Silk.NET.Input; +public partial interface IAxisDevice +{ + static sealed void Validate(IAxisDevice device); +} ``` From 340907a98934da748596b8f499ccce66849033f0 Mon Sep 17 00:00:00 2001 From: dom Date: Sun, 26 Jan 2025 10:40:23 -0500 Subject: [PATCH 14/25] Add flags for "dynamic" axes and axis groups --- .../Proposal - Axis Input Devices.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index 4fdc5b1013..2dbba66bfb 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -250,6 +250,19 @@ public enum AxisTrait : ulong /// DeltaOnly = 1 << 27 | Delta | RawValueOnly, + /// + /// Indicates that this axis has been added at runtime, and requires validation at first sight. + /// Example use cases: touch pads and touch screens, the results of object sensors, etc + /// Also suggests this axis's index is subject to change, or is liable to be removed. As a result, axes marked with this flag must not precede any axes not marked with this flag. + /// Axes marked with this flag may not be included in any AxisGroup unless the AxisGroup is marked with the AxisGroupType.Dynamic flag as well. + /// It is highly recommended that axes defined in this way be grouped in the same way one would group a struct - if multiple axes per-item (in this case, a finger) + /// are present, these should be contiguous. for example, in the case of a touch surface: + /// Finger 1 begins at index i + /// i is an X component, i + 1 is a Y component, and i + 2 is a pressure component, + /// Finger 2 begins at i + 3 following the same pattern + /// + Dynamic = 1 << 28u, + /// /// Used for axes that are on the left side of a device from the user's perspective. /// Useful for left/right handedness accommodation and various symmetrical devices.

@@ -473,6 +486,14 @@ public enum AxisGroupType : ulong Accelerometer = 1u << 11, // 3D accelerometer, requires 3 axes, XYZ order DeviceInformation = 1u << 24, // battery level, microphone level, etc + + /// + /// Indicates that this axis has been added at runtime, and requires validation at first sight. + /// Example use cases: touch pads and touch screens, the results of object sensors, etc + /// Also suggests this groups's index is subject to change, or is liable to be removed. As a result, groups marked with this flag must not precede any groups not marked with this flag. + /// + Dynamic = 1u << 28, + LeftHanded = 1u << 30, // allowing for left/right swap of symmetrical devices and labeling left/right buttons RightHanded = 1u << 31, // allowing for left/right swap of symmetrical devices } From c83ed9c53f1cb02fef0721b565eb382d31d497c7 Mon Sep 17 00:00:00 2001 From: dom Date: Sun, 26 Jan 2025 12:26:50 -0500 Subject: [PATCH 15/25] Update axis update method with a struct and add "dynamic" axis handling --- .../proposals/Proposal - Axis Input Devices.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index 2dbba66bfb..f245796681 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -131,9 +131,15 @@ This is how a developer could subscribe directly to the axis changes of an `IAxi ```csharp +/// +/// Represents the state of a single axis +/// +/// True if the axis is currently in use / actively updated (required to be true if axis is not ) +public readonly record struct AxisState(IAxisDevice Device, AxisDescription Description, float Value, bool IsActive = true); + public interface IAxisInputHandler { - public void OnAxisChanged(IAxisDevice device, in AxisDescription description, float value); + public void OnAxisChanged(in AxisState state); } ``` @@ -253,7 +259,7 @@ public enum AxisTrait : ulong /// /// Indicates that this axis has been added at runtime, and requires validation at first sight. /// Example use cases: touch pads and touch screens, the results of object sensors, etc - /// Also suggests this axis's index is subject to change, or is liable to be removed. As a result, axes marked with this flag must not precede any axes not marked with this flag. + /// As a result of being added at runtime, axes marked with this flag must not precede any axes not marked with this flag. /// Axes marked with this flag may not be included in any AxisGroup unless the AxisGroup is marked with the AxisGroupType.Dynamic flag as well. /// It is highly recommended that axes defined in this way be grouped in the same way one would group a struct - if multiple axes per-item (in this case, a finger) /// are present, these should be contiguous. for example, in the case of a touch surface: @@ -490,7 +496,7 @@ public enum AxisGroupType : ulong /// /// Indicates that this axis has been added at runtime, and requires validation at first sight. /// Example use cases: touch pads and touch screens, the results of object sensors, etc - /// Also suggests this groups's index is subject to change, or is liable to be removed. As a result, groups marked with this flag must not precede any groups not marked with this flag. + /// As a result of being added at runtime, groups marked with this flag must not precede any groups not marked with this flag. /// Dynamic = 1u << 28, From 547ec9c2ca738b0d90942c899df2d5e702d3517a Mon Sep 17 00:00:00 2001 From: dom Date: Sun, 26 Jan 2025 13:10:36 -0500 Subject: [PATCH 16/25] add IAxisDevice.Outputs draft to the proposal --- .../Proposal - Axis Input Devices.md | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index f245796681..524b854614 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -640,3 +640,79 @@ public bool IsInDeadzone(IAxisDevice device, int descriptionIndex, Deadzone dead public bool IsGroupInDeadzone(IAxisDevice device, int groupIndex, ReadOnlySpan deadzones); } ``` + +# Outputs +Many controllers have haptics, LEDs, etc, that would be nice to access in a generic, normalized way as well. The following is a first draft concept for this functionality that could accomplish this while leaving room for different sorts of outputs later on. + +Feedback requested: is custom data handling necessary here? At that point it violates the "axis" concept pretty strongly and is best served by higher-level interfaces, but I wanted to make sure we at least consider these things. + +```csharp +public partial interface IAxisDevice +{ + /// + /// Output definitions for this device - commonly haptics, LEDs, etc + /// + IReadOnlyList Outputs { get; } + + /// + /// Allows the caller to set an output value for a specific output index. + /// Valid for any axis not marked as . + /// + public void SetOutput(int index, float value); + + /// + /// Allows the caller to set an output value(s) for a non-floating-point output. + /// Valid only for axes marked as . + /// Requires that the caller knows the correct data type for the output, and as such the implementation must throw + /// an exception if the data type is not correct. + ///

+ /// More than likely, concrete implementations or higher-level interfaces will provide a safer way to set these + /// sorts of values. The absence of higher-level utilities for this method should be considered a missing feature + /// if the implementation utilizes . + ///

+ /// It is more or less expected that this will have few, if any, implementations in the early stages of this API. + /// Its use is reserved for advanced use-cases or higher-level implementations where the caller knows the exact + /// data type required the output - for example, setting a specific bit pattern for a specific LED, driving + /// embedded displays, built-in speakers, etc. + ///
+ /// The index of the in + /// A pointer to the value(s) to be set + public void SetOutput(int index, T* values, int count = 1) where T : unmanaged => throw new NotImplementedException(); +} + +public enum OutputAxisTrait : ulong +{ + None = 0, + LED = 1u << 0, + Haptic = 1u << 1, + + LEDBrightness = 1 << 4 | LED, + LEDRed = 1u << 5 | LED, + LEDGreen = 1u << 6 | LED, + LEDBlue = 1u << 7 | LED, + LEDWhite = 1u << 8 | LED, + + ForceFeedback = 1u << 2 | Haptic, + VibrationMotor = 1u << 3 | Haptic, + + /// + /// For firmware-controlled device settings, such as microphone gain, calibration parameters, etc. Necessarily requires additional descriptors that should be expanded upon in this or another proposal. + /// In the case of a "virtual" device, this could apply to any device setting that is specific to that device. + /// + DeviceControl = 1u << 4. + + RawDataOnly = AxisTrait.RawValueOnly, + LeftSide = AxisTrait.LeftSide, + RightSide = AxisTrait.RightSide +} + +/// +/// The definition of an output of an +/// +/// The index of this output in +/// An associated axis index if one exists - for example, force feedback for a +/// specific trigger, an LED for a specific button, etc. +/// An optional name for the output +/// Valid (and required) only for outputs marked as +public readonly record struct OutputDescription(int Index, OutputAxisTrait Traits, int? AssociatedAxisIndex = null, string? Name = null, Type? CustomDataType = null); +``` From f9589eadd67d66e55381ba8e7a50a7f9ac1a09d5 Mon Sep 17 00:00:00 2001 From: dom Date: Sun, 26 Jan 2025 13:11:46 -0500 Subject: [PATCH 17/25] correct axis group example --- documentation/proposals/Proposal - Axis Input Devices.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index 524b854614..5b68dcef32 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -560,7 +560,7 @@ Though no such API is defined in this proposal, the Silk.NET team reserves the r Below is an incomplete example of how an `AxisGroup` list can be populated. ```csharp -private static readonly IReadOnlyList Families = +private static readonly IReadOnlyList Groups = [ new(0, AxisGroupType.Joystick2D | AxisGroupType.LeftHanded, [0, 1, 2, 3], 1, "Left Joystick"), new(1, AxisGroupType.Joystick2D | AxisGroupType.RightHanded, [4, 5, 6, 7], 0, "Right Joystick"), @@ -569,6 +569,8 @@ private static readonly IReadOnlyList Families = new(5, AxisGroupType.Position2D, [18, 19], null, "Touchpad"), new(6, AxisGroupType.Unknown, [20, 21], null, "Unknown") ]; + +IReadOnlyList IAxisDevice.Groups => Groups; ``` From 93efc34c29564d44589d6751137835f907f3e0e2 Mon Sep 17 00:00:00 2001 From: dom Date: Sun, 26 Jan 2025 13:19:32 -0500 Subject: [PATCH 18/25] update OutputDescription constructors --- .../Proposal - Axis Input Devices.md | 57 +++++++++++++++++-- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index 5b68dcef32..f390640d64 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -708,13 +708,58 @@ public enum OutputAxisTrait : ulong RightSide = AxisTrait.RightSide } + /// /// The definition of an output of an /// -/// The index of this output in -/// An associated axis index if one exists - for example, force feedback for a -/// specific trigger, an LED for a specific button, etc. -/// An optional name for the output -/// Valid (and required) only for outputs marked as -public readonly record struct OutputDescription(int Index, OutputAxisTrait Traits, int? AssociatedAxisIndex = null, string? Name = null, Type? CustomDataType = null); +public readonly record struct OutputDescription +{ + /// + /// The index of this output in + /// + public int Index { get; init; } + + public OutputAxisTrait Traits { get; init; } + + /// + /// An associated axis index if one exists - for example, force feedback for a + /// specific trigger, an LED for a specific button, etc. + /// + public int? AssociatedAxisIndex { get; init; } + + /// + /// An optional name for the output + /// + public string? Name { get; init; } + + /// + /// Valid (and required) only for outputs marked as + /// + public Type? DataType { get; init; } + + /// + /// The definition of an output of an + /// + /// The index of this output in + /// The traits of this output + /// An associated axis index if one exists - for example, force feedback for a + /// specific trigger, an LED for a specific button, etc. + /// An optional name for the output + public OutputDescription(int index, OutputAxisTrait traits, int? associatedAxisIndex = null, string? name = null) : + this(index, traits, typeof(float), name, associatedAxisIndex) + { + } + + /// + /// Valid (and required) only for outputs marked as + public OutputDescription(int index, OutputAxisTrait traits, Type dataType, string? name, + int? associatedAxisIndex = null) + { + Index = index; + Traits = traits; + AssociatedAxisIndex = associatedAxisIndex; + Name = name; + DataType = dataType; + } +} ``` From e8c7a8d02990e0591e7ee4bc3bacec53c9ef4a1c Mon Sep 17 00:00:00 2001 From: dom Date: Sun, 26 Jan 2025 13:48:42 -0500 Subject: [PATCH 19/25] change IsActive to AxisDescription.IsAvailable --- .../Proposal - Axis Input Devices.md | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index f390640d64..3b0b30f73a 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -134,8 +134,7 @@ This is how a developer could subscribe directly to the axis changes of an `IAxi /// /// Represents the state of a single axis /// -/// True if the axis is currently in use / actively updated (required to be true if axis is not ) -public readonly record struct AxisState(IAxisDevice Device, AxisDescription Description, float Value, bool IsActive = true); +public readonly record struct AxisState(IAxisDevice Device, AxisDescription Description, float Value); public interface IAxisInputHandler { @@ -156,16 +155,35 @@ There are two primary structures that give meaning to the axes of an `IAxisDevic /// the index of the axis in the device /// the traints of the axis, described above /// A human-readable name for the axis -/// The bounds of the raw value - a value of 'default' indicates it is typical (0, 1), or -/// in the case that the provided traits are , this must be default and indicates no known bounds. +/// +/// +/// If a given axis is unavailable for any reason during runtime, this would be marked as "false". This field is not included +/// in the validation process.
+/// Axes with the flag are expected to make liberal use of this value in order to keep their axis indices consistent for the lifetime of each axis. +/// +/// +/// +/// The bounds of the raw value - a value of 'default' indicates it is typical (0, 1), or +/// in the case that the provided traits are , this must be default and indicates no known bounds. +/// public readonly record struct AxisDescription( int Index, AxisTrait Traits, string Name, + bool IsAvailable = true, Vector2 RawValueBounds = default) { public static implicit operator int(in AxisDescription description) => description.Index; } + +public static class AxisDescriptionExtensions +{ + /// + /// Creates a copy of the given axis description with a modified value. + /// + /// Request for feedback: naming? + public static AxisDescription AsAvailable(this AxisDescription description, bool available); +} ``` ### Axis Traits From d30c7962e65d3b396e233fab20a603d2aa9ba784 Mon Sep 17 00:00:00 2001 From: dom Date: Sun, 26 Jan 2025 13:52:42 -0500 Subject: [PATCH 20/25] remove type information from OutputDescription --- .../proposals/Proposal - Axis Input Devices.md | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index 3b0b30f73a..dc23d6046e 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -749,11 +749,6 @@ public readonly record struct OutputDescription /// An optional name for the output ///
public string? Name { get; init; } - - /// - /// Valid (and required) only for outputs marked as - /// - public Type? DataType { get; init; } /// /// The definition of an output of an @@ -763,21 +758,12 @@ public readonly record struct OutputDescription /// An associated axis index if one exists - for example, force feedback for a /// specific trigger, an LED for a specific button, etc. /// An optional name for the output - public OutputDescription(int index, OutputAxisTrait traits, int? associatedAxisIndex = null, string? name = null) : - this(index, traits, typeof(float), name, associatedAxisIndex) - { - } - - /// - /// Valid (and required) only for outputs marked as - public OutputDescription(int index, OutputAxisTrait traits, Type dataType, string? name, - int? associatedAxisIndex = null) + public OutputDescription(int index, OutputAxisTrait traits, int? associatedAxisIndex = null, string? name = null) { Index = index; Traits = traits; AssociatedAxisIndex = associatedAxisIndex; Name = name; - DataType = dataType; } } ``` From 3eb813fa8e93220611d1a996315ecd2da8b91ac8 Mon Sep 17 00:00:00 2001 From: dom Date: Sun, 26 Jan 2025 14:03:44 -0500 Subject: [PATCH 21/25] add output groups --- .../Proposal - Axis Input Devices.md | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index dc23d6046e..dd0d2bac65 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -673,6 +673,9 @@ public partial interface IAxisDevice /// Output definitions for this device - commonly haptics, LEDs, etc /// IReadOnlyList Outputs { get; } + + // for groups of outputs, e.g. all LEDs, all vibration motors, left side vibration motors, etc + IReadOnlyList OutputGroups { get; } /// /// Allows the caller to set an output value for a specific output index. @@ -700,18 +703,12 @@ public partial interface IAxisDevice public void SetOutput(int index, T* values, int count = 1) where T : unmanaged => throw new NotImplementedException(); } -public enum OutputAxisTrait : ulong +public enum OutputTrait : ulong { None = 0, LED = 1u << 0, Haptic = 1u << 1, - LEDBrightness = 1 << 4 | LED, - LEDRed = 1u << 5 | LED, - LEDGreen = 1u << 6 | LED, - LEDBlue = 1u << 7 | LED, - LEDWhite = 1u << 8 | LED, - ForceFeedback = 1u << 2 | Haptic, VibrationMotor = 1u << 3 | Haptic, @@ -737,7 +734,7 @@ public readonly record struct OutputDescription /// public int Index { get; init; } - public OutputAxisTrait Traits { get; init; } + public OutputTrait Traits { get; init; } /// /// An associated axis index if one exists - for example, force feedback for a @@ -749,6 +746,8 @@ public readonly record struct OutputDescription /// An optional name for the output /// public string? Name { get; init; } + + public bool IsAvailable {get; init;} /// /// The definition of an output of an @@ -758,12 +757,40 @@ public readonly record struct OutputDescription /// An associated axis index if one exists - for example, force feedback for a /// specific trigger, an LED for a specific button, etc. /// An optional name for the output - public OutputDescription(int index, OutputAxisTrait traits, int? associatedAxisIndex = null, string? name = null) + public OutputDescription(int index, OutputTrait traits, int? associatedAxisIndex = null, string? name = null, bool isAvailable = true) { Index = index; Traits = traits; AssociatedAxisIndex = associatedAxisIndex; Name = name; + IsAvailable = isAvailable; } } + +public readonly record struct OutputGroup(int index, OutputAxisGroupType Purpose, IReadOnlyList AssociatedOutputs, string? Name = null + +public enum OutputGroupType : ulong +{ + None = 0, + + // Indicates that this group controls an LED or group of LEDs + // Depending on length: + // length == 1, this is brightness + // length == 3, RGB + // length == 4, RGBA (RGB + Brightness) + LED = 1u << 0, + + Haptics = 1u << 1, + Vibration = 1u << 2 | Haptics + + /// + /// Indicates that this axis has been added at runtime, and requires validation at first sight. + /// Example use cases: touch pads and touch screens, the results of object sensors, etc + /// As a result of being added at runtime, groups marked with this flag must not precede any groups not marked with this flag. + /// + Dynamic = 1u << 28, + + LeftSide = AxisGroupType.LeftHanded, // allowing for left/right swap of symmetrical devices + RightSide = AxisGroupType.RightHanded, // allowing for left/right swap of symmetrical devices +} ``` From 8822f9892f2cc4affca43b7651592461e1123129 Mon Sep 17 00:00:00 2001 From: dom Date: Sat, 1 Feb 2025 15:17:19 -0500 Subject: [PATCH 22/25] address some feedback points --- .../proposals/Proposal - Axis Input Devices.md | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index dd0d2bac65..47e109d10a 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -175,15 +175,6 @@ public readonly record struct AxisDescription( { public static implicit operator int(in AxisDescription description) => description.Index; } - -public static class AxisDescriptionExtensions -{ - /// - /// Creates a copy of the given axis description with a modified value. - /// - /// Request for feedback: naming? - public static AxisDescription AsAvailable(this AxisDescription description, bool available); -} ``` ### Axis Traits @@ -219,9 +210,8 @@ public enum AxisTrait : ulong Rotation = 1 << 3 | Orientation, /// - /// Indicates that this axis is a component of a euler angle (do we want this to be in degrees or radians?) - /// If this flag is set, the axis is expected to be normalized between 0-360 degrees, with wrapping applied - /// It is required you do NOT set , as it will be assumed to be (0, 360/2pi). + /// Indicates that this axis is a component of a euler angle in radians + /// It is required you do NOT set , as it will be assumed to be *-pi, pi) The raw value must be pre-wrapped within range. /// // this is specified because single axes of euler angle components can be useful by themselves, @@ -501,7 +491,7 @@ public enum AxisGroupType : ulong /// Position3D = 1u << 8, - // should this be 6 and 8 axes, similar to how a 2d joystick is defined by 4 axes? + // XYZ order, aka (pitch, yaw, roll) RotationEuler = 1u << 9, // gyroscope, gyroscope + magnetomete, VR peripheral rotation - requires 3 axes, XYZ order RotationQuaternion = @@ -546,7 +536,7 @@ The following is a chart laying out example orderings for different axis groups. | ![WASD keys](https://upload.wikimedia.org/wikipedia/commons/0/05/Cursor_keys--WASD.svg) | (A, D, S, W)
Arrow keys would be (left, right, down, up of course)
(J, L, K, I), etc | | ![Fight stick](https://upload.wikimedia.org/wikipedia/commons/8/8f/Wii_Arcade_Stick.png?20200918233300) | The "face" buttons are not a candidate for `AxisGroupType.FourFaceButtons` designation due to their layout, so fight sticks and some other retro controllers would likely exclude their face buttons from any particular axis group

The joystick would of course be (-X Left, +X Right, -Y Down, +Y Up) | | ![Accelerometer](https://cdn.phidgets.com/docs/images/9/96/Accelerometer_Intro.jpg) | (X, Y, Z) | -| ![Gyroscope](https://upload.wikimedia.org/wikipedia/commons/b/b8/Roll_Pitch_Yaw.JPG) | Euler representation: (yaw, pitch, roll)

Quaternion representation: (X, Y, Z, W) | +| ![Gyroscope](https://upload.wikimedia.org/wikipedia/commons/b/b8/Roll_Pitch_Yaw.JPG) | Euler representation: (pitch, yaw, roll)

Quaternion representation: (X, Y, Z, W) | As you may have gathered, a single axis can be assigned multiple axis groups. This is perfectly legal and *encouraged*. However, the ordering of such groups can become significant to users of the device. From bf12cf9ce82b9f87b754c50abb25a86a6ae3a3ea Mon Sep 17 00:00:00 2001 From: dom Date: Sat, 1 Feb 2025 15:32:18 -0500 Subject: [PATCH 23/25] improve enums according to feedback (and then some) --- .../Proposal - Axis Input Devices.md | 109 +++++++++--------- 1 file changed, 57 insertions(+), 52 deletions(-) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index 47e109d10a..e57e7a0841 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -201,13 +201,13 @@ public enum AxisTrait : ulong // The same default behavior will apply to any axis being treated as a Binary input, whether or not it has this trait. Binary = 1 << 1, - // an analog axis that explicitly does not return to center or zero like a joystick or trigger - // things like touch-pads, mice, or world-space positions - Point = 1 << 8 | Analog | HasRawValue, + // an analog axis that explicitly does not return to center or zero + // things like touch-pads, mice, or world-space positions - NOT joysticks or triggers, as those would simply be marked as `Analog` + Point = 1 << 2 | Analog | HasRawValue, // ---- Physical orientation ---- - Orientation = 1 << 2 | Analog, - Rotation = 1 << 3 | Orientation, + Orientation = 1 << 2 | Analog, // indicates that this axis pertains to a real-world orientation of a device or tracked object + Rotation = 1 << 3 | Orientation, // indicates that this axis pertains to a real-world rotation of a device or tracked object /// /// Indicates that this axis is a component of a euler angle in radians @@ -232,13 +232,28 @@ public enum AxisTrait : ulong /// Position = 1 << 7 | Orientation | Point, // ----------------------------- + /// + /// Used for axes that are on the left side of a device from the user's perspective. + /// Useful for left/right handedness accommodation and various symmetrical devices.

+ /// For example: a left stick on a gamepad, a left trigger, a left shoulder button, the DPad on a gamepad, etc + ///
+ LeftSide = 1 << 12, + + /// + /// Used for axes that are on the right side of a device from the user's perspective. + /// Useful for left/right handedness accommodation and various symmetrical devices.

+ /// For example: a right stick on a gamepad, a right trigger, a right shoulder button, the face buttons on a gamepad, etc + ///
+ RightSide = 1 << 13, + + /// /// Indicates that the raw value provided for this axis is not the same as the normalized value.
/// If this flag is signaled on an axis, it is highly recommended that the bounds are provided as well, even if they are infinite /// This can be useful for things like euler angle components, where single axes 0-360 degrees (or 0-2pi radians? do we want to standardize which?) can be useful by themselves ///
- HasRawValue = 1 << 24 | Analog, + HasRawValue = 1 << 58 | Analog, /// /// Indicates that there is no one-size-fits-all way to normalize this axis, and must be handled in a case-by-case basis
@@ -249,12 +264,12 @@ public enum AxisTrait : ulong ///
/// If this is set, it is expected that the axis is NOT provided any raw value constraints ///
- RawValueOnly = 1 << 25 | HasRawValue, + RawValueOnly = 1 << 59 | HasRawValue, /// /// Things like a mouse movement, trackpad, etc /// - Delta = 1 << 26 | HasRawValue, + Delta = 1 << 60 | HasRawValue, /// /// Indicates that there is no one-size-fits-all way to normalize this axis, and must be handled in a case-by-case basis
@@ -262,7 +277,10 @@ public enum AxisTrait : ulong /// - Mouse position
/// - Trackpad without an API for "absolute" position ///
- DeltaOnly = 1 << 27 | Delta | RawValueOnly, + DeltaOnly = 1 << 61 | Delta | RawValueOnly, + + DeviceInformation = 1u << 62, // battery level, microphone level, etc + /// /// Indicates that this axis has been added at runtime, and requires validation at first sight. @@ -275,21 +293,7 @@ public enum AxisTrait : ulong /// i is an X component, i + 1 is a Y component, and i + 2 is a pressure component, /// Finger 2 begins at i + 3 following the same pattern /// - Dynamic = 1 << 28u, - - /// - /// Used for axes that are on the left side of a device from the user's perspective. - /// Useful for left/right handedness accommodation and various symmetrical devices.

- /// For example: a left stick on a gamepad, a left trigger, a left shoulder button, the DPad on a gamepad, etc - ///
- LeftSide = 1 << 30, - - /// - /// Used for axes that are on the right side of a device from the user's perspective. - /// Useful for left/right handedness accommodation and various symmetrical devices.

- /// For example: a right stick on a gamepad, a right trigger, a right shoulder button, the face buttons on a gamepad, etc - ///
- RightSide = 1 << 31, + Dynamic = 1u << 63u, } ``` @@ -431,19 +435,23 @@ public enum AxisGroupType : ulong // (West, East, South, North) / (Left, Right, Down, Up) / (X, B, A, Y) - xbox / (square, circle, cross, triangle) - playstation // 4 independent axes using the ordering convention above - FourFaceButtons = 1u << 1, + DiamondActionButtons = 1u << 1, - // Specified in addition to FourFaceButtons due to the constraints often placed on DPad axes from a hardware level + // Two buttons standardly used as confirmation or rejection of a given selection + // things like A & B buttons, X & circle buttons, Enter and Backspace/ESC, etc + ConfirmReject = 1 << 2 + + // Specified in addition to DiamondActionButtons due to the constraints often placed on DPad axes from a hardware level // 2 Axes, (X, Y) (-1, 1) // made of 4 physical axes, (X-, X+, Y-, Y+) - DPad = 1u << 2 | FourFaceButtons, - + DPad = 1u << 3 | DiamondActionButtons, + /// - /// 1D joystick, 1 logical axis, (-1, 1) + /// 1D joystick, 1 logical axis, (-1, 1) - intended to allow mapping to a particular physical axis (XYZ) of a 2D+ joystick, or to represent a 1-dimensional input stick /// made of 2 physical axes, (X-, X+) - /// More physically resembling a "lever" than a joystick + /// More physically resembling a "lever" than a joystick if not a component of a 2D+ joystick /// - Joystick1D = 1u << 3 | DPad, + JoystickAxis = 1u << 4 | DPad, /// /// 2D joystick, 2 logical axes, (X, Y) (-1, 1) @@ -451,7 +459,7 @@ public enum AxisGroupType : ulong /// If constructed with 5 axes, the 5th axis is the "pressure" axis for a pointer /// More abstractly, this is a Position2D that returns to 0,0 when released /// - Joystick2D = 1u << 4 | Position2D | DPad, + Joystick2D = 1u << 5 | Position2D | DPad, /// /// 3D joystick @@ -461,16 +469,15 @@ public enum AxisGroupType : ulong /// Can be used for analog stick with a press-in button, or a 3D mouse, etc /// More abstractly, this is a Position3D that returns to 0,0,0 when released /// - Joystick3D = 1u << 5 | Position3D, + Joystick3D = 1u << 6 | Position3D, /// - /// 1D touch surface or tracker + /// 1D touch surface or tracker axis - intended to allow mapping to an individual axis of a 2D+ position surface, or to represent a 1D touch strip, trackball, & similar /// 1 logical axis, (-1, 1) /// made of 2 physical axes, (-X, +X). /// If constructed with 3 axes, the 3rd axis is the "pressure" axis for a pointer - /// Physically, this can be a represented as a touch strip /// - Position1D = 1u << 6, + PositionAxis = 1u << 7, /// /// 2D touch surface or tracker @@ -480,7 +487,7 @@ public enum AxisGroupType : ulong /// * Request for feedback: do we want to allow this to be defined with 2 "physical" axes as well? /// Wherein if it is provided with 3 "physical" axes, the 3rd is the "pressure" axis /// - Position2D = 1u << 7, + Position2D = 1u << 8, /// /// 3D touch surface or tracker, 3 logical axes, (X, Y, Z) @@ -489,32 +496,30 @@ public enum AxisGroupType : ulong /// * Request for feedback: do we want to allow this to be defined with 3 "physical" axes as well? /// Wherein if it is provided with 4 "physical" axes, the 4th is the "pressure" axis /// - Position3D = 1u << 8, + Position3D = 1u << 9, // XYZ order, aka (pitch, yaw, roll) - RotationEuler = 1u << 9, // gyroscope, gyroscope + magnetomete, VR peripheral rotation - requires 3 axes, XYZ order + RotationEuler = 1u << 10, // gyroscope, gyroscope + magnetomete, VR peripheral rotation - requires 3 axes, XYZ order RotationQuaternion = - 1u << 10, // gyroscope, gyroscope + magnetomete, VR peripheral rotation - requires 4 axes, XYZW order + 1u << 11, // gyroscope, gyroscope + magnetomete, VR peripheral rotation - requires 4 axes, XYZW order - Accelerometer = 1u << 11, // 3D accelerometer, requires 3 axes, XYZ order + Accelerometer = 1u << 12, // 3D accelerometer, requires 3 axes, XYZ order - DeviceInformation = 1u << 24, // battery level, microphone level, etc + LeftHanded = 1u << 13, // allowing for left/right swap of symmetrical devices and labeling left/right buttons + RightHanded = 1u << 14, // allowing for left/right swap of symmetrical devices /// /// Indicates that this axis has been added at runtime, and requires validation at first sight. /// Example use cases: touch pads and touch screens, the results of object sensors, etc /// As a result of being added at runtime, groups marked with this flag must not precede any groups not marked with this flag. /// - Dynamic = 1u << 28, - - LeftHanded = 1u << 30, // allowing for left/right swap of symmetrical devices and labeling left/right buttons - RightHanded = 1u << 31, // allowing for left/right swap of symmetrical devices + Dynamic = 1u << 63, } ``` ### Axis Group Directional Convention -This pertains to `AxisGroupType`s that suggest positional input - i.e. `FourFaceButtons`, `DPad`, `Joystick1D`, `Joystick2D`, `Joystick3D`, `Position1D`, `Position2D`, `Position3D`, `RotationEuler`, `RotationQuaternion`, and `Accelerometer`. The convention is as follows: +This pertains to `AxisGroupType`s that suggest positional input - i.e. `DiamondActionButtons`, `DPad`, `Joystick1D`, `Joystick2D`, `Joystick3D`, `Position1D`, `Position2D`, `Position3D`, `RotationEuler`, `RotationQuaternion`, and `Accelerometer`. The convention is as follows: Note that `AxisGroup` sets a standard for dealing with multi-dimensional axes, which dictates the order in which `AxisGroup.Axes` is populated by `AxisDescription` indices. The convention to be followed by all comparable axis groups is as follows: @@ -534,7 +539,7 @@ The following is a chart laying out example orderings for different axis groups. | ![playstation face buttons](https://upload.wikimedia.org/wikipedia/commons/thumb/c/c4/PlayStation_original_controller_face_buttons.jpg/684px-PlayStation_original_controller_face_buttons.jpg) | (Square, Circle, Cross, Triangle) | | ![N64 face buttons](https://upload.wikimedia.org/wikipedia/commons/4/48/Nintendo_64_controller_face_buttons.jpg) | Primary - (C Left, C Right, C Down, C Up)

Secondary - (B, C down, A, C left) | | ![WASD keys](https://upload.wikimedia.org/wikipedia/commons/0/05/Cursor_keys--WASD.svg) | (A, D, S, W)
Arrow keys would be (left, right, down, up of course)
(J, L, K, I), etc | -| ![Fight stick](https://upload.wikimedia.org/wikipedia/commons/8/8f/Wii_Arcade_Stick.png?20200918233300) | The "face" buttons are not a candidate for `AxisGroupType.FourFaceButtons` designation due to their layout, so fight sticks and some other retro controllers would likely exclude their face buttons from any particular axis group

The joystick would of course be (-X Left, +X Right, -Y Down, +Y Up) | +| ![Fight stick](https://upload.wikimedia.org/wikipedia/commons/8/8f/Wii_Arcade_Stick.png?20200918233300) | The "face" buttons are not a candidate for `AxisGroupType.DiamondActionButtons` designation due to their layout, so fight sticks and some other retro controllers would likely exclude their face buttons from any particular axis group

The joystick would of course be (-X Left, +X Right, -Y Down, +Y Up) | | ![Accelerometer](https://cdn.phidgets.com/docs/images/9/96/Accelerometer_Intro.jpg) | (X, Y, Z) | | ![Gyroscope](https://upload.wikimedia.org/wikipedia/commons/b/b8/Roll_Pitch_Yaw.JPG) | Euler representation: (pitch, yaw, roll)

Quaternion representation: (X, Y, Z, W) | @@ -549,13 +554,13 @@ In general, the larger order of axis groups is not significant except for the fo Though it is possible (and often recommended) to use `AxisGroupType` as flags, including every supported `AxisGroupType` associated with a set of axes, doing so is not required. It may be advantageous to utilize the above ordering constraint in order to define a preference for a particular set of axes to be used as a specific `AxisGroupType`. For example, defining a D-Pad on a controller that could be defined as: ```csharp -new(3, AxisGroupType.DPad | AxisGroupType.FourFaceButtons | AxisGroupType.LeftHanded, [14, 15, 16, 17], 6, "D-Pad"), +new(3, AxisGroupType.DPad | AxisGroupType.DiamondActionButtons | AxisGroupType.LeftHanded, [14, 15, 16, 17], 6, "D-Pad"), ``` could instead be defined as: ```csharp new(3, AxisGroupType.DPad | AxisGroupType.LeftHanded, [14, 15, 16, 17], 6, "D-Pad"), -new(4, AxisGroupType.FourFaceButtons | AxisGroupType.LeftHanded, [14, 15, 16, 17], 6, "D-Pad"), +new(4, AxisGroupType.DiamondActionButtons | AxisGroupType.LeftHanded, [14, 15, 16, 17], 6, "D-Pad"), ``` As previously stated, the latter grants you the benefit of having a defined order of primacy for the axes in the D-Pad group, which can be useful for certain implementations. @@ -572,8 +577,8 @@ private static readonly IReadOnlyList Groups = [ new(0, AxisGroupType.Joystick2D | AxisGroupType.LeftHanded, [0, 1, 2, 3], 1, "Left Joystick"), new(1, AxisGroupType.Joystick2D | AxisGroupType.RightHanded, [4, 5, 6, 7], 0, "Right Joystick"), - new(3, AxisGroupType.DPad | AxisGroupType.FourFaceButtons | AxisGroupType.LeftHanded, [14, 15, 16, 17], 6, "D-Pad"), - new(4, AxisGroupType.FourFaceButtons | AxisGroupType.RightHanded, [30, 31, 32, 33], 5, "Face Buttons"), + new(3, AxisGroupType.DPad | AxisGroupType.DiamondActionButtons | AxisGroupType.LeftHanded, [14, 15, 16, 17], 6, "D-Pad"), + new(4, AxisGroupType.DiamondActionButtons | AxisGroupType.RightHanded, [30, 31, 32, 33], 5, "Face Buttons"), new(5, AxisGroupType.Position2D, [18, 19], null, "Touchpad"), new(6, AxisGroupType.Unknown, [20, 21], null, "Unknown") ]; From 7df22dfdf83e265fb0564625a071efd50bea0c29 Mon Sep 17 00:00:00 2001 From: dom Date: Sat, 1 Feb 2025 15:33:11 -0500 Subject: [PATCH 24/25] formatting --- documentation/proposals/Proposal - Axis Input Devices.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index e57e7a0841..12b1013e44 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -212,10 +212,9 @@ public enum AxisTrait : ulong /// /// Indicates that this axis is a component of a euler angle in radians /// It is required you do NOT set , as it will be assumed to be *-pi, pi) The raw value must be pre-wrapped within range. + /// this is specified because single axes of euler angle components can be useful by themselves, + /// whereas anything shorter than 3 or 4 axes is not useful for quaternion rotation components /// - - // this is specified because single axes of euler angle components can be useful by themselves, - // whereas anything shorter than 3 or 4 axes is not useful for quaternion rotation components EulerAngleComponent = 1 << 4 | Rotation | HasRawValue, /// From 608b07e7a42c9ad2036b72cd5fb467c7ab394ff0 Mon Sep 17 00:00:00 2001 From: dom Date: Sat, 1 Feb 2025 15:41:56 -0500 Subject: [PATCH 25/25] update OutputGroupType to be consistent with previous changes --- documentation/proposals/Proposal - Axis Input Devices.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/documentation/proposals/Proposal - Axis Input Devices.md b/documentation/proposals/Proposal - Axis Input Devices.md index 12b1013e44..451686e7d5 100644 --- a/documentation/proposals/Proposal - Axis Input Devices.md +++ b/documentation/proposals/Proposal - Axis Input Devices.md @@ -777,14 +777,15 @@ public enum OutputGroupType : ulong Haptics = 1u << 1, Vibration = 1u << 2 | Haptics + + LeftSide = AxisGroupType.LeftHanded, // allowing for left/right swap of symmetrical devices + RightSide = AxisGroupType.RightHanded, // allowing for left/right swap of symmetrical devices + /// /// Indicates that this axis has been added at runtime, and requires validation at first sight. /// Example use cases: touch pads and touch screens, the results of object sensors, etc /// As a result of being added at runtime, groups marked with this flag must not precede any groups not marked with this flag. /// - Dynamic = 1u << 28, - - LeftSide = AxisGroupType.LeftHanded, // allowing for left/right swap of symmetrical devices - RightSide = AxisGroupType.RightHanded, // allowing for left/right swap of symmetrical devices + Dynamic = AxisGroupType.Dynamic, } ```