Skip to content
Open
7 changes: 7 additions & 0 deletions Assets/Tests/InputSystem/CoreTests_Actions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3748,6 +3748,13 @@ public void Actions_ResettingDevice_CancelsOngoingActionsThatAreDrivenByIt()
// does not cause the action to start back up. For pass-through actions, that is different
// as *any* value change performs the action. So here, we see *both* a cancellation and then
// immediately a performing of the action.
//
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be rephrased into a decision when decision exists after review/discussion.

// ISXB-1097 DESIGN NOTE: Whether pass-through actions should emit Performed(0f) on reset is
// debatable. Emitting it is consistent with the pass-through contract ("any value
// change performs"). Suppressing it would be consistent with button/value actions and
// the idea that resets aren't real user input. If we decide to suppress, the synthetic
// reset event in ResetDevice should be explicitly marked as handled rather than
// relying on the old eventId=-1 sentinel side-effect (see InputManager.cs:1656).
Assert.That(passThroughActionTrace, Canceled(passThroughAction).AndThen(Performed(passThroughAction, value: 0f)));
}
}
Expand Down
49 changes: 37 additions & 12 deletions Assets/Tests/InputSystem/CoreTests_Actions_Rebinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1135,22 +1135,34 @@ public void Actions_InteractiveRebinding_IfDeviceHasMultipleUsages_UsagesAreAppl
}
}

// It can be desirable to not let the event through that we're rebinding from. This, for example, prevents the event
// from triggering UI actions. Note, however, that it also prevents the state of the device from updating correctly.
//
// NOTE: Hopefully, when we have a system in place that allows coordinating event consumption between actions, we have
// have a more elegant solution at our hand for solving the problem here.
[Test]
// ISXB-1097: Verifies event suppression behavior during interactive rebinding under both
// event handled policies. Under SuppressStateUpdates, handled events are discarded entirely
// (device state not updated). Under SuppressActionEventNotifications, handled events still
// propagate state but suppress action-level notifications (e.g. WasPressedThisFrame).
#pragma warning disable CS0618 // Type or member is obsolete
[TestCase(InputEventHandledPolicy.SuppressStateUpdates)]
#pragma warning restore CS0618 // Type or member is obsolete
[TestCase(InputEventHandledPolicy.SuppressActionEventNotifications)]
[Category("Actions")]
public void Actions_InteractiveRebinding_CanSuppressEventsWhileListening()
public void Actions_InteractiveRebinding_CanSuppressEventsWhileListening(InputEventHandledPolicy policy)
{
InputSystem.manager.inputEventHandledPolicy = policy;
#pragma warning disable CS0618 // Type or member is obsolete
var stateUpdatesAreSuppressed = policy == InputEventHandledPolicy.SuppressStateUpdates;
#pragma warning restore CS0618 // Type or member is obsolete

var gamepad = InputSystem.AddDevice<Gamepad>();
var mouse = InputSystem.AddDevice<Mouse>();

var action = new InputAction(binding: "<Gamepad>/buttonNorth");
var rebindAction = new InputAction(binding: "<Gamepad>/buttonNorth");

// Separate action to verify WasPressedThisFrame suppression behavior.
var observerAction = new InputAction(name: "observer", type: InputActionType.Button,
binding: "<Gamepad>/buttonSouth");
observerAction.Enable();

using (new InputActionRebindingExtensions.RebindingOperation()
.WithAction(action)
.WithAction(rebindAction)
.WithControlsExcluding("<Pointer>/position")
.WithControlsExcluding("<Pointer>/press")
.WithControlsExcluding("<Mouse>/leftButton")
Expand All @@ -1159,17 +1171,30 @@ public void Actions_InteractiveRebinding_CanSuppressEventsWhileListening()
.Start()
)
{
// Non-bindable controls should not be suppressed and continue working as normal
// Non-bindable controls should not be suppressed and continue working as normal.
Set(mouse.position, new Vector2(123, 234));
Press(mouse.leftButton);
Press(gamepad.buttonEast);

Press(gamepad.buttonSouth);
Assert.That(action.bindings[0].overridePath, Is.EqualTo("<Gamepad>/buttonSouth"));
Assert.That(rebindAction.bindings[0].overridePath, Is.EqualTo("<Gamepad>/buttonSouth"));
Assert.That(mouse.leftButton.isPressed, Is.True);
Assert.That(gamepad.buttonSouth.isPressed, Is.False);
Assert.That(gamepad.buttonEast.isPressed, Is.True);
Assert.That(mouse.position.ReadValue(), Is.EqualTo(new Vector2(123, 234)).Using(Vector2EqualityComparer.Instance));

// ISXB-1097: Under SuppressStateUpdates, the handled event is discarded so device
// state is not updated. Under SuppressActionEventNotifications, state propagates
// normally — only action notifications are suppressed.
Assert.That(gamepad.buttonSouth.isPressed, Is.EqualTo(!stateUpdatesAreSuppressed));

// ISXB-1097: WasPressedThisFrame is an action-level pull API and should return
// false under both policies, but for different reasons:
// - SuppressStateUpdates: the event is discarded before state updates, so the
// action never observes the press at all.
// - SuppressActionEventNotifications: state propagates (device sees the press)
// but InputAction.WasPressedThisFrame() is gated by the per-action suppressed
// flag (TriggerState.isSuppressed) and returns false.
Assert.That(observerAction.WasPressedThisFrame(), Is.False);
}
}

Expand Down
Loading
Loading