Skip to content

Commit 9a76136

Browse files
authored
FIX: Device lost to InputUser after layout changes (case 1347320, #1468).
1 parent b356ce7 commit 9a76136

5 files changed

Lines changed: 85 additions & 12 deletions

File tree

Assets/Tests/InputSystem/Plugins/PlayerInputTests.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2142,6 +2142,39 @@ public void PlayerInput_WhenJoinActionIsAReference_JoiningIsStillPossibleAfterDe
21422142
Assert.That(playerJoined, Is.True);
21432143
}
21442144

2145+
[Test] // Mimics what is reported in https://issuetracker.unity3d.com/product/unity/issues/guid/1347320
2146+
[Category("PlayerInput")]
2147+
public void PlayerInput_WhenOverridingDeviceLayout_LostDeviceShouldBeResolvedAndRepaired()
2148+
{
2149+
var go = new GameObject();
2150+
var playerInput = go.AddComponent<PlayerInput>();
2151+
playerInput.actions = InputActionAsset.FromJson(kActions);
2152+
var gamepad = InputSystem.AddDevice<Gamepad>();
2153+
go.SetActive(true);
2154+
2155+
// Actuate gamepad to pair with user (other option would be initially paired)
2156+
Press(gamepad.buttonSouth);
2157+
Assert.That(playerInput.devices[0], Is.SameAs(gamepad));
2158+
2159+
// Register a layout override (this will recreate device)
2160+
InputSystem.RegisterLayoutOverride(@"
2161+
{
2162+
""name"" : ""GamepadPlayerUsageTags"",
2163+
""extend"" : ""Gamepad"",
2164+
""commonUsages"" : [
2165+
""Player1"", ""Player2""
2166+
]
2167+
}
2168+
");
2169+
2170+
// As reported in https://issuetracker.unity3d.com/product/unity/issues/guid/1347320
2171+
// there would be no device assigned after registered layout override since this
2172+
// would recreate the device with the same device id (but a new instance).
2173+
Assert.That(playerInput.devices.Count, Is.EqualTo(1));
2174+
Assert.That(playerInput.devices[0], !Is.SameAs(gamepad)); // expected replacement (by design, not a requirement)
2175+
Assert.That(playerInput.devices[0].name, Is.EqualTo(gamepad.name));
2176+
}
2177+
21452178
// An action is either
21462179
// (a) button-like, or
21472180
// (b) axis-like, or

Assets/Tests/InputSystem/Utilities/ArrayHelperTests.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,17 @@ public void Utilities_IndexOfReference__IsUsingReferenceEqualsAndConstrainedBySt
117117
Assert.AreEqual(1, arr.IndexOfReference(arr[1], 1, 3));
118118
Assert.AreEqual(2, arr.IndexOfReference(arr[2], 1, 3));
119119
}
120+
121+
[Test]
122+
[Category("Utilities")]
123+
public void Utilities_IndexOfPredicate__IsUsingPredicateForEqualityAndConstraintedByStartIndexAndCount()
124+
{
125+
var arr = new int[] { 0, 1, 2, 3, 4, 5 };
126+
127+
Assert.That(arr.IndexOf(x => x >= 3, 0, arr.Length), Is.EqualTo(3));
128+
Assert.That(arr.IndexOf(x => x >= 3, 0, 3), Is.EqualTo(-1));
129+
Assert.That(arr.IndexOf(x => x >= 3, 1, 3), Is.EqualTo(3));
130+
Assert.That(arr.IndexOf(x => x >= 3, 4, 0), Is.EqualTo(-1));
131+
Assert.That(arr.IndexOf(x => x < 0, 3, 3), Is.EqualTo(-1));
132+
}
120133
}

Packages/com.unity.inputsystem/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ however, it has to be formatted properly to pass verification tests.
3030
- Fixed an issue with `InputSystemUIInputModule` that would cause UI to stop responding during play mode after changing a script file while Recompile and Continue mode is active, or by forcing a script recompile using `RequestScriptCompilation`([case 1324215](https://issuetracker.unity3d.com/product/unity/issues/guid/1324215/)).
3131
- Fixed `InputSystemUIInputModule` inspector showing all action bindings as "None" when assigned a runtime created actions asset ([case 1304943](https://issuetracker.unity3d.com/issues/input-system-ui-input-module-loses-prefab-action-mapping-in-local-co-op)).
3232
- Fixed a problem with UI Toolkit buttons remaining active when multiple fingers are used on a touchscreen, using `InputSystemUIInputModule` with pointerBehavior set to `UIPointerBehavior.SingleUnifiedPointer`. UI Toolkit will now always receive the same pointerId when that option is in use, regardless of the hardware component that produced the pointer event. ([case 1369081](https://issuetracker.unity3d.com/issues/transitions-get-stuck-when-pointer-behavior-is-set-to-single-unified-pointer-and-multiple-touches-are-made)).
33+
- Fixed a problem with `InputUser` where devices would be removed and not added again after layout overrides preventing certain devices, e.g. gamepads to not work correctly when associated with action map bindings tied to `PlayerInput` ([case 1347320](https://issuetracker.unity3d.com/product/unity/issues/guid/1347320)).
3334
- Fixed DualSense on iOS not inheriting from `DualShockGamepad` ([case 1378308](https://issuetracker.unity3d.com/issues/input-dualsense-detection-ios)).
3435
- Fixed a device becoming `.current` (e.g. `Gamepad.current`, etc) when sending a new state event that contains no control changes (case 1377952).
3536
- Fixed calling `IsPressed` on an entire device returning `true` ([case 1374024](https://issuetracker.unity3d.com/issues/inputcontrol-dot-ispressed-always-returns-true-when-using-new-input-system)).

Packages/com.unity.inputsystem/InputSystem/Plugins/Users/InputUser.cs

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,10 +1506,10 @@ private static void OnDeviceChange(InputDevice device, InputDeviceChange change)
15061506
// New device was added. See if it was a device we previously lost on a user.
15071507
case InputDeviceChange.Added:
15081508
{
1509-
// Could be a previously lost device. Could affect multiple users. Repeatedly search in
1510-
// s_AllLostDevices until we can't find the device anymore.
1511-
var deviceIndex = s_GlobalState.allLostDevices.IndexOfReference(device, s_GlobalState.allLostDeviceCount);
1512-
while (deviceIndex != -1)
1509+
// Search all lost devices. Could affect multiple users.
1510+
// Note that RemoveDeviceFromUser removes one element, hence no advancement of deviceIndex.
1511+
for (var deviceIndex = FindLostDevice(device); deviceIndex != -1;
1512+
deviceIndex = FindLostDevice(device, deviceIndex))
15131513
{
15141514
// Find user. Must be there as we found the device in s_AllLostDevices.
15151515
var userIndex = -1;
@@ -1523,20 +1523,15 @@ private static void OnDeviceChange(InputDevice device, InputDeviceChange change)
15231523
}
15241524
}
15251525

1526-
// Remove from list of lost devices. No notification.
1527-
RemoveDeviceFromUser(userIndex, device, asLostDevice: true);
1526+
// Remove from list of lost devices. No notification. Notice that we need to use device
1527+
// from lost device list even if its another instance.
1528+
RemoveDeviceFromUser(userIndex, s_GlobalState.allLostDevices[deviceIndex], asLostDevice: true);
15281529

15291530
// Notify.
15301531
Notify(userIndex, InputUserChange.DeviceRegained, device);
15311532

15321533
// Add back as normally paired device.
15331534
AddDeviceToUser(userIndex, device);
1534-
1535-
// Search for another user who had lost the same device.
1536-
// Note: s_AllLostDevices is modified (element erased) from within RemoveDeviceFromUser,
1537-
// hence, deviceIndex is not advanced and s_AllLostDeviceCount is one less than
1538-
// previous linear search iteration.
1539-
deviceIndex = s_GlobalState.allLostDevices.IndexOfReference(device, deviceIndex, s_GlobalState.allLostDeviceCount);
15401535
}
15411536
break;
15421537
}
@@ -1603,6 +1598,22 @@ private static void OnDeviceChange(InputDevice device, InputDeviceChange change)
16031598
}
16041599
}
16051600

1601+
private static int FindLostDevice(InputDevice device, int startIndex = 0)
1602+
{
1603+
// Compare both by device ID and by reference. We may be looking at a device that was recreated
1604+
// due to layout changes (new InputDevice instance, same ID) or a device that was reconnected
1605+
// and thus fetched out of `disconnectedDevices` (same InputDevice instance, new ID).
1606+
1607+
var newDeviceId = device.deviceId;
1608+
for (var i = startIndex; i < s_GlobalState.allLostDeviceCount; ++i)
1609+
{
1610+
var lostDevice = s_GlobalState.allLostDevices[i];
1611+
if (device == lostDevice || lostDevice.deviceId == newDeviceId) return i;
1612+
}
1613+
1614+
return -1;
1615+
}
1616+
16061617
// We hook this into InputSystem.onEvent when listening for activity on unpaired devices.
16071618
// What this means is that we get to run *before* state reaches the device. This in turn
16081619
// means that should the device get paired as a result, actions that are enabled as part

Packages/com.unity.inputsystem/InputSystem/Utilities/ArrayHelpers.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,21 @@ public static int IndexOf<TValue>(this TValue[] array, Predicate<TValue> predica
176176
return -1;
177177
}
178178

179+
public static int IndexOf<TValue>(this TValue[] array, Predicate<TValue> predicate, int startIndex = 0, int count = -1)
180+
{
181+
if (array == null)
182+
return -1;
183+
184+
var end = startIndex + (count < 0 ? array.Length - startIndex : count);
185+
for (var i = startIndex; i < end; ++i)
186+
{
187+
if (predicate(array[i]))
188+
return i;
189+
}
190+
191+
return -1;
192+
}
193+
179194
public static int IndexOfReference<TFirst, TSecond>(this TFirst[] array, TSecond value, int count = -1)
180195
where TSecond : class
181196
where TFirst : TSecond

0 commit comments

Comments
 (0)