Skip to content

Commit e8da3bb

Browse files
FIX: GetHapticCapabilitiesCommand always failing to execute (#1776)
* Fixed GetHapticCapabilitiesCommand always failing to execute and added missing haptic capabilities properties * Added setter to make migration better if the user was setting the old field * Fixed API_MinorVersionsHaveNoBreakingChanges by reverting renames The names won't match the UnityEngine.XR and IUnityXRInput.h names, but this is needed to avoid a breaking change. Renamed the optimal size field to more closely match the naming of maxBufferSize for consistency within Input System. * Reverted redundant explicit field initialization since it was causing API_MinorVersionsHaveNoBreakingChanges to fail. The value remains the same. The test could be updated in the future to recognize the value is the same, but for now it's easier to just revert the change. * Added xml-doc to public fields and methods --------- Co-authored-by: James McGill <[email protected]>
1 parent aaab20f commit e8da3bb

File tree

5 files changed

+133
-9
lines changed

5 files changed

+133
-9
lines changed

Assets/Tests/InputSystem/Plugins/XRTests.cs

+15
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,7 @@ public void Controls_OptimizedControls_PoseControl_IsOptimized()
11401140

11411141
// ISXB-405
11421142
[Test]
1143+
[Category("Devices")]
11431144
public void Devices_AddingUnusualDevice_ShouldntCrashTheSystem()
11441145
{
11451146
var deviceDescr =
@@ -1153,5 +1154,19 @@ public void Devices_AddingUnusualDevice_ShouldntCrashTheSystem()
11531154

11541155
Assert.That(device, Is.Not.Null);
11551156
}
1157+
1158+
[Test]
1159+
[Category("Commands")]
1160+
public void Commands_GetHapticCapabilitiesCommand_UsesCorrectPayloadSize()
1161+
{
1162+
unsafe
1163+
{
1164+
// Check that the payload of the command matches the low-level struct defined in IUnityXRInput.h (UnityXRHapticCapabilities)
1165+
// and used in XRInputSubsystem by checking the size. The sizes are required to match for the event to be
1166+
// sent to the device.
1167+
Assert.That(sizeof(UnityEngine.InputSystem.XR.Haptics.HapticCapabilities), Is.EqualTo(sizeof(UnityEngine.XR.HapticCapabilities)));
1168+
Assert.That(sizeof(UnityEngine.InputSystem.XR.Haptics.GetHapticCapabilitiesCommand) - InputDeviceCommand.BaseCommandSize, Is.EqualTo(sizeof(UnityEngine.XR.HapticCapabilities)));
1169+
}
1170+
}
11561171
}
11571172
#endif

Packages/com.unity.inputsystem/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ however, it has to be formatted properly to pass verification tests.
4646
- Fixed issue of visual elements being null during editing project-wide actions in project settings which prompted console errors.
4747
- Fixed case ISX-1436 (UI TK Input Action Asset Editor - Error deleting Bindings with DeleteKey on Windows).
4848
- Fixed issue with UI Toolkit based Input Action Editor not restoring it's selected items after Domain Reload.
49+
- Fixed the [`GetHapticCapabilitiesCommand`](xref:UnityEngine.InputSystem.XR.Haptics.GetHapticCapabilitiesCommand) always failing to execute due to a mismatch in the size in bytes of the payload and the size expected by XR devices. Changed [`HapticCapabilities`](xref:UnityEngine.InputSystem.XR.Haptics.HapticCapabilities) to include all properties returned by the XR input subsystem. This makes Input System match the functionality provided by the [XR](https://docs.unity3d.com/Manual/com.unity.modules.xr.html) module's [`InputDevice.TryGetHapticCapabilities`](https://docs.unity3d.com/ScriptReference/XR.InputDevice.TryGetHapticCapabilities.html) and [`HapticCapabilities`](https://docs.unity3d.com/ScriptReference/XR.HapticCapabilities.html).
50+
4951

5052
## [1.8.0-pre.1] - 2023-09-04
5153

Packages/com.unity.inputsystem/InputSystem/Actions/Composites/Vector2Composite.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public class Vector2Composite : InputBindingComposite<Vector2>
124124
/// </code>
125125
/// </example>
126126
/// </remarks>
127-
public Mode mode = Mode.DigitalNormalized;
127+
public Mode mode;
128128

129129
/// <inheritdoc />
130130
public override Vector2 ReadValue(ref InputBindingCompositeContext context)

Packages/com.unity.inputsystem/InputSystem/Devices/Commands/IInputDeviceCommandInfo.cs

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ namespace UnityEngine.InputSystem.LowLevel
77
/// </summary>
88
public interface IInputDeviceCommandInfo
99
{
10+
/// <summary>
11+
/// The data format identifier of the device command as a <see cref="FourCC"/> code.
12+
/// </summary>
1013
FourCC typeStatic { get; }
1114
}
1215
}

Packages/com.unity.inputsystem/InputSystem/Plugins/XR/Haptics/GetHapticCapabilitiesCommand.cs

+112-8
Original file line numberDiff line numberDiff line change
@@ -6,43 +6,147 @@
66

77
namespace UnityEngine.InputSystem.XR.Haptics
88
{
9+
/// <summary>
10+
/// Describes the haptic capabilities of a specific device.
11+
/// </summary>
912
public struct HapticCapabilities
1013
{
11-
public HapticCapabilities(uint numChannels, uint frequencyHz, uint maxBufferSize)
14+
/// <summary>
15+
/// Initializes and returns an instance of <see cref="HapticCapabilities"/>.
16+
/// </summary>
17+
/// <param name="numChannels">The number of haptic channels available on this device.</param>
18+
/// <param name="supportsImpulse">This device supports sending a haptic impulse.</param>
19+
/// <param name="supportsBuffer">This device supports sending a haptic buffer.</param>
20+
/// <param name="frequencyHz">The buffer frequency the device operates at in Hertz.</param>
21+
/// <param name="maxBufferSize">The max amount of buffer data that can be stored by the device.</param>
22+
/// <param name="optimalBufferSize">The optimal size of a device's buffer, taking into account frequency and latency.</param>
23+
public HapticCapabilities(uint numChannels, bool supportsImpulse, bool supportsBuffer, uint frequencyHz, uint maxBufferSize, uint optimalBufferSize)
1224
{
1325
this.numChannels = numChannels;
26+
this.supportsImpulse = supportsImpulse;
27+
this.supportsBuffer = supportsBuffer;
1428
this.frequencyHz = frequencyHz;
1529
this.maxBufferSize = maxBufferSize;
30+
this.optimalBufferSize = optimalBufferSize;
1631
}
1732

18-
public uint numChannels { get; private set; }
19-
public uint frequencyHz { get; private set; }
20-
public uint maxBufferSize { get; private set; }
33+
/// <summary>
34+
/// Deprecated. Use <see cref="HapticCapabilities(uint, bool, bool, uint, uint, uint)"/> instead.
35+
/// This constructor did not match the native haptic capabilities struct and was missing properties.
36+
/// </summary>
37+
/// <param name="numChannels">The number of haptic channels available on this device.</param>
38+
/// <param name="frequencyHz">The buffer frequency the device operates at in Hertz.</param>
39+
/// <param name="maxBufferSize">The max amount of buffer data that can be stored by the device.</param>
40+
public HapticCapabilities(uint numChannels, uint frequencyHz, uint maxBufferSize)
41+
: this(numChannels, false, false, frequencyHz, maxBufferSize, 0U)
42+
{
43+
}
44+
45+
/// <summary>
46+
/// The number of haptic channels available on this device.
47+
/// </summary>
48+
public uint numChannels { get; }
49+
50+
/// <summary>
51+
/// This device supports sending a haptic impulse.
52+
/// </summary>
53+
/// <seealso cref="SendHapticImpulseCommand"/>
54+
public bool supportsImpulse { get; }
55+
56+
/// <summary>
57+
/// This device supports sending a haptic buffer.
58+
/// </summary>
59+
/// <seealso cref="SendBufferedHapticCommand"/>
60+
public bool supportsBuffer { get; }
61+
62+
/// <summary>
63+
/// The buffer frequency the device operates at in Hertz. This impacts how fast the device consumes buffered haptic data.
64+
/// </summary>
65+
/// <remarks>
66+
/// This value is greater than 0 if <see cref="supportsBuffer"/> is <see langword="true"/>, and 0 otherwise.
67+
/// </remarks>
68+
public uint frequencyHz { get; }
69+
70+
/// <summary>
71+
/// The max amount of buffer data that can be stored by the device.
72+
/// </summary>
73+
public uint maxBufferSize { get; }
74+
75+
/// <summary>
76+
/// The optimal size of a device's buffer, taking into account frequency and latency.
77+
/// </summary>
78+
public uint optimalBufferSize { get; }
2179
}
2280

81+
/// <summary>
82+
/// Input device command struct for retrieving the haptic capabilities of a device.
83+
/// </summary>
2384
[StructLayout(LayoutKind.Explicit, Size = kSize)]
2485
public struct GetHapticCapabilitiesCommand : IInputDeviceCommandInfo
2586
{
2687
static FourCC Type => new FourCC('X', 'H', 'C', '0');
2788

28-
const int kSize = InputDeviceCommand.kBaseCommandSize + sizeof(uint) * 3;
89+
// 20 bytes of data from uint(4) + bool(1) + bool(1) + padding + uint(4) + uint(4) + uint(4)
90+
const int kSize = InputDeviceCommand.kBaseCommandSize + 20;
2991

92+
/// <inheritdoc />
3093
public FourCC typeStatic => Type;
3194

3295
[FieldOffset(0)]
3396
InputDeviceCommand baseCommand;
3497

98+
/// <summary>
99+
/// The number of haptic channels available on this device.
100+
/// </summary>
35101
[FieldOffset(InputDeviceCommand.kBaseCommandSize)]
36102
public uint numChannels;
37103

38-
[FieldOffset(InputDeviceCommand.kBaseCommandSize + sizeof(uint))]
104+
/// <summary>
105+
/// This device supports sending a haptic impulse.
106+
/// </summary>
107+
/// <seealso cref="SendHapticImpulseCommand"/>
108+
[FieldOffset(InputDeviceCommand.kBaseCommandSize + 4)]
109+
public bool supportsImpulse;
110+
111+
/// <summary>
112+
/// This device supports sending a haptic buffer.
113+
/// </summary>
114+
/// <seealso cref="SendBufferedHapticCommand"/>
115+
[FieldOffset(InputDeviceCommand.kBaseCommandSize + 5)]
116+
public bool supportsBuffer;
117+
118+
/// <summary>
119+
/// The buffer frequency the device operates at in Hertz. This impacts how fast the device consumes buffered haptic data.
120+
/// </summary>
121+
/// <remarks>
122+
/// This value is greater than 0 if <see cref="supportsBuffer"/> is <see langword="true"/>, and 0 otherwise.
123+
/// </remarks>
124+
[FieldOffset(InputDeviceCommand.kBaseCommandSize + 8)]
39125
public uint frequencyHz;
40126

41-
[FieldOffset(InputDeviceCommand.kBaseCommandSize + (sizeof(uint) * 2))]
127+
/// <summary>
128+
/// The max amount of buffer data that can be stored by the device.
129+
/// </summary>
130+
[FieldOffset(InputDeviceCommand.kBaseCommandSize + 12)]
42131
public uint maxBufferSize;
43132

44-
public HapticCapabilities capabilities => new HapticCapabilities(numChannels, frequencyHz, maxBufferSize);
133+
/// <summary>
134+
/// The optimal size of a device's buffer, taking into account frequency and latency.
135+
/// </summary>
136+
[FieldOffset(InputDeviceCommand.kBaseCommandSize + 16)]
137+
public uint optimalBufferSize;
138+
139+
/// <summary>
140+
/// The haptic capabilities of the device, populated after this command is executed.
141+
/// </summary>
142+
public HapticCapabilities capabilities => new HapticCapabilities(numChannels, supportsImpulse, supportsBuffer, frequencyHz, maxBufferSize, optimalBufferSize);
45143

144+
/// <summary>
145+
/// Creates and returns a new initialized input device command struct for retrieving
146+
/// the haptic capabilities of a device when executed.
147+
/// </summary>
148+
/// <returns>Returns a new command struct with the data header initialized, making it ready to execute.</returns>
149+
/// <seealso cref="InputDevice.ExecuteCommand{TCommand}(ref TCommand)"/>
46150
public static GetHapticCapabilitiesCommand Create()
47151
{
48152
return new GetHapticCapabilitiesCommand

0 commit comments

Comments
 (0)