Skip to content

Commit 0ddd534

Browse files
timkeoPauliusd01
andauthored
FIX: Composite binding isn't triggered after ResetDevice() called during Action handler (ISXB-746) (#1893)
* Modifies ProcessControlStateChange() so ProcessButtonState() still called even if Composite already triggered * Adds a test to cover this corner-case scenario Co-authored-by: Paulius Dervinis <[email protected]>
1 parent 82b53d6 commit 0ddd534

File tree

3 files changed

+135
-23
lines changed

3 files changed

+135
-23
lines changed

Assets/Tests/InputSystem/CoreTests_Actions.cs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12029,4 +12029,99 @@ public void Actions_ActionMapDisabledDuringOnAfterSerialization()
1202912029
Assert.That(map.enabled, Is.True);
1203012030
Assert.That(map.FindAction("MyAction", true).enabled, Is.True);
1203112031
}
12032+
12033+
// ResetDevice wasn't properly clearly Composite key state, i.e. BindingState.pressTime
12034+
// https://jira.unity3d.com/browse/ISXB-746
12035+
[Test]
12036+
[TestCase(false)]
12037+
[TestCase(true)]
12038+
[Category("Actions")]
12039+
public void Actions_CompositeBindingResetWhenResetDeviceCalledWhileExecutingAction(bool useTwoModifierComposite)
12040+
{
12041+
var keyboard = InputSystem.AddDevice<Keyboard>();
12042+
bool actionPerformed;
12043+
12044+
// Enables "Modifier must be pressed first" behavior on all Composite Bindings
12045+
InputSystem.settings.shortcutKeysConsumeInput = true;
12046+
12047+
const string modifier1 = "<Keyboard>/shift";
12048+
const string modifier2 = "<Keyboard>/ctrl";
12049+
const string key = "<Keyboard>/F1";
12050+
12051+
var map = new InputActionMap();
12052+
var resetAction = map.AddAction("resetAction");
12053+
12054+
if (!useTwoModifierComposite)
12055+
{
12056+
resetAction.AddCompositeBinding("OneModifier")
12057+
.With("Modifier", modifier1)
12058+
.With("Binding", key);
12059+
}
12060+
else
12061+
{
12062+
resetAction.AddCompositeBinding("TwoModifiers")
12063+
.With("Modifier1", modifier1)
12064+
.With("Modifier2", modifier2)
12065+
.With("Binding", key);
12066+
}
12067+
12068+
resetAction.performed += (InputAction.CallbackContext ctx) =>
12069+
{
12070+
// Disable the Keyboard while action is being performed.
12071+
// This simulates an "OnFocusLost" event occurring while processing the Action, e.g. when switching primary displays or moving the main window
12072+
actionPerformed = true;
12073+
InputSystem.s_Manager.EnableOrDisableDevice(keyboard.device, false, InputManager.DeviceDisableScope.TemporaryWhilePlayerIsInBackground);
12074+
};
12075+
12076+
map.Enable();
12077+
12078+
actionPerformed = false;
12079+
Press(keyboard.leftShiftKey);
12080+
Press(keyboard.leftCtrlKey);
12081+
Press(keyboard.f1Key);
12082+
12083+
Assert.IsTrue(actionPerformed);
12084+
12085+
// Re enable the Keyboard (before keys are released) and execute Action again
12086+
InputSystem.s_Manager.EnableOrDisableDevice(keyboard.device, true, InputManager.DeviceDisableScope.TemporaryWhilePlayerIsInBackground);
12087+
12088+
actionPerformed = false;
12089+
Release(keyboard.leftShiftKey);
12090+
Release(keyboard.leftCtrlKey);
12091+
Release(keyboard.f1Key);
12092+
12093+
Press(keyboard.leftCtrlKey);
12094+
Press(keyboard.leftShiftKey);
12095+
Press(keyboard.f1Key);
12096+
12097+
Assert.IsTrue(actionPerformed);
12098+
12099+
actionPerformed = false;
12100+
Release(keyboard.leftCtrlKey);
12101+
Release(keyboard.leftShiftKey);
12102+
Release(keyboard.f1Key);
12103+
12104+
// Re enable the Keyboard (after keys are released) and execute Action one more time
12105+
InputSystem.s_Manager.EnableOrDisableDevice(keyboard.device, true, InputManager.DeviceDisableScope.TemporaryWhilePlayerIsInBackground);
12106+
12107+
Press(keyboard.leftCtrlKey);
12108+
Press(keyboard.leftShiftKey);
12109+
Press(keyboard.f1Key);
12110+
12111+
Assert.IsTrue(actionPerformed);
12112+
12113+
actionPerformed = false;
12114+
Press(keyboard.leftShiftKey);
12115+
Press(keyboard.leftCtrlKey);
12116+
Press(keyboard.f1Key);
12117+
12118+
// Re enable the Keyboard (before keys are released) and verify Action isn't triggered when Key pressed first
12119+
InputSystem.s_Manager.EnableOrDisableDevice(keyboard.device, true, InputManager.DeviceDisableScope.TemporaryWhilePlayerIsInBackground);
12120+
12121+
Press(keyboard.f1Key);
12122+
Press(keyboard.leftCtrlKey);
12123+
Press(keyboard.leftShiftKey);
12124+
12125+
Assert.IsFalse(actionPerformed);
12126+
}
1203212127
}

Packages/com.unity.inputsystem/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Due to package verification, the latest version below is the unpublished version
99
however, it has to be formatted properly to pass verification tests.
1010

1111
## [Unreleased] - yyyy-mm-dd
12+
- Fixed Composite binding isn't triggered after ResetDevice() called during Action handler [ISXB-746](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-746)
1213

1314
## [1.8.2] - 2024-04-29
1415

Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1464,37 +1464,43 @@ private void ProcessControlStateChange(int mapIndex, int controlIndex, int bindi
14641464
// If the binding is part of a composite, check for interactions on the composite
14651465
// itself and give them a first shot at processing the value change.
14661466
var haveInteractionsOnComposite = false;
1467+
var compositeAlreadyTriggered = false;
14671468
if (bindingStatePtr->isPartOfComposite)
14681469
{
14691470
var compositeBindingIndex = bindingStatePtr->compositeOrCompositeBindingIndex;
14701471
var compositeBindingPtr = &bindingStates[compositeBindingIndex];
14711472

1472-
// If the composite has already been triggered from the very same event, ignore it.
1473+
// If the composite has already been triggered from the very same event set a flag so it isn't triggered again.
14731474
// Example: KeyboardState change that includes both A and W key state changes and we're looking
14741475
// at a WASD composite binding. There's a state change monitor on both the A and the W
14751476
// key and thus the manager will notify us individually of both changes. However, we
14761477
// want to perform the action only once.
1477-
if (ShouldIgnoreInputOnCompositeBinding(compositeBindingPtr, eventPtr))
1478-
return;
1479-
1480-
// Update magnitude for composite.
1481-
var compositeIndex = bindingStates[compositeBindingIndex].compositeOrCompositeBindingIndex;
1482-
var compositeContext = new InputBindingCompositeContext
1478+
// NOTE: Do NOT ignore this Event, we still need finish processing the individual button states.
1479+
if (!ShouldIgnoreInputOnCompositeBinding(compositeBindingPtr, eventPtr))
14831480
{
1484-
m_State = this,
1485-
m_BindingIndex = compositeBindingIndex
1486-
};
1487-
trigger.magnitude = composites[compositeIndex].EvaluateMagnitude(ref compositeContext);
1488-
memory.compositeMagnitudes[compositeIndex] = trigger.magnitude;
1489-
1490-
// Run through interactions on composite.
1491-
var interactionCountOnComposite = compositeBindingPtr->interactionCount;
1492-
if (interactionCountOnComposite > 0)
1481+
// Update magnitude for composite.
1482+
var compositeIndex = bindingStates[compositeBindingIndex].compositeOrCompositeBindingIndex;
1483+
var compositeContext = new InputBindingCompositeContext
1484+
{
1485+
m_State = this,
1486+
m_BindingIndex = compositeBindingIndex
1487+
};
1488+
trigger.magnitude = composites[compositeIndex].EvaluateMagnitude(ref compositeContext);
1489+
memory.compositeMagnitudes[compositeIndex] = trigger.magnitude;
1490+
1491+
// Run through interactions on composite.
1492+
var interactionCountOnComposite = compositeBindingPtr->interactionCount;
1493+
if (interactionCountOnComposite > 0)
1494+
{
1495+
haveInteractionsOnComposite = true;
1496+
ProcessInteractions(ref trigger,
1497+
compositeBindingPtr->interactionStartIndex,
1498+
interactionCountOnComposite);
1499+
}
1500+
}
1501+
else
14931502
{
1494-
haveInteractionsOnComposite = true;
1495-
ProcessInteractions(ref trigger,
1496-
compositeBindingPtr->interactionStartIndex,
1497-
interactionCountOnComposite);
1503+
compositeAlreadyTriggered = true;
14981504
}
14991505
}
15001506

@@ -1503,21 +1509,31 @@ private void ProcessControlStateChange(int mapIndex, int controlIndex, int bindi
15031509
// one of higher magnitude) or may even lead us to switch to processing a different binding
15041510
// (e.g. when an input of previously greater magnitude has now fallen below the level of another
15051511
// ongoing input with now higher magnitude).
1506-
var isConflictingInput = IsConflictingInput(ref trigger, actionIndex);
1507-
bindingStatePtr = &bindingStates[trigger.bindingIndex]; // IsConflictingInput may switch us to a different binding.
1512+
//
1513+
// If Composite has already been triggered, skip this step; it's unnecessary and could also
1514+
// cause a processing issue if we switch to another binding.
1515+
var isConflictingInput = false;
1516+
if (!compositeAlreadyTriggered)
1517+
{
1518+
isConflictingInput = IsConflictingInput(ref trigger, actionIndex);
1519+
bindingStatePtr = &bindingStates[trigger.bindingIndex]; // IsConflictingInput may switch us to a different binding.
1520+
}
15081521

15091522
// Process button presses/releases.
1523+
// We MUST execute this processing even if Composite has already been triggered to ensure button states
1524+
// are properly updated (ISXB-746)
15101525
if (!isConflictingInput)
15111526
ProcessButtonState(ref trigger, actionIndex, bindingStatePtr);
15121527

15131528
// If we have interactions, let them do all the processing. The presence of an interaction
15141529
// essentially bypasses the default phase progression logic of an action.
1530+
// Interactions are skipped if compositeAlreadyTriggered is set.
15151531
var interactionCount = bindingStatePtr->interactionCount;
15161532
if (interactionCount > 0 && !bindingStatePtr->isPartOfComposite)
15171533
{
15181534
ProcessInteractions(ref trigger, bindingStatePtr->interactionStartIndex, interactionCount);
15191535
}
1520-
else if (!haveInteractionsOnComposite && !isConflictingInput)
1536+
else if (!haveInteractionsOnComposite && !isConflictingInput && !compositeAlreadyTriggered)
15211537
{
15221538
ProcessDefaultInteraction(ref trigger, actionIndex);
15231539
}

0 commit comments

Comments
 (0)