diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ae22be0d1e..5feb0bd422 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -11,8 +11,8 @@ _Please describe the testing already done by you and what testing you request/re _Please rate the potential complexity and halo effect from low to high for the reviewers. Note down potential risks to specific Editor branches if any._ -- Complexity: -- Halo Effect: +- Complexity: +- Halo Effect: ### Comments to reviewers @@ -45,7 +45,3 @@ During merge: - `DOCS: ___`. - `CHANGE: ___`. - `RELEASE: 1.1.0-preview.3`. - -After merge: - -- [ ] Create forward/backward port if needed. If you are blocked from creating a forward port now please add a task to ISX-1444. diff --git a/Assets/Tests/InputSystem/CoreTests_Actions.cs b/Assets/Tests/InputSystem/CoreTests_Actions.cs index 0a4d551fdd..da9fb18f3c 100644 --- a/Assets/Tests/InputSystem/CoreTests_Actions.cs +++ b/Assets/Tests/InputSystem/CoreTests_Actions.cs @@ -642,6 +642,55 @@ public void Actions_DoNotGetTriggeredByEditorUpdates() } } + [Test] + [Category("Actions")] + [Description("Tests that that only the latest event after focus is regained is able to trigger the action." + + "Depends on background behavior. ")] + [TestCase(InputSettings.BackgroundBehavior.IgnoreFocus)] + [TestCase(InputSettings.BackgroundBehavior.ResetAndDisableNonBackgroundDevices)] + [TestCase(InputSettings.BackgroundBehavior.ResetAndDisableAllDevices)] + public void Actions_DoNotGetTriggeredByOutOfFocusEventInEditor(InputSettings.BackgroundBehavior backgroundBehavior) + { + InputSystem.settings.backgroundBehavior = backgroundBehavior; + + var mouse = InputSystem.AddDevice(); + var mousePointAction = new InputAction(binding: "/position", type: InputActionType.PassThrough); + mousePointAction.Enable(); + + using (var trace = new InputActionTrace(mousePointAction)) + { + currentTime += 1.0f; + runtime.PlayerFocusLost(); + currentTime += 1.0f; + // Queuing an event like it would be in the editor when the GameView is out of focus. + Set(mouse.position, new Vector2(0.234f, 0.345f) , queueEventOnly: true); + currentTime += 1.0f; + // Gaining focus like it would happen in the editor when the GameView regains focus. + runtime.PlayerFocusGained(); + currentTime += 1.0f; + // This emulates a device sync that happens when the player regains focus through an IOCTL command. + // That's why it also has it's time incremented. + Set(mouse.position, new Vector2(1.0f, 2.0f), queueEventOnly: true); + currentTime += 1.0f; + // This update should not trigger any ction as it's an editor update. + InputSystem.Update(InputUpdateType.Editor); + currentTime += 1.0f; + + var actions = trace.ToArray(); + Assert.That(actions, Has.Length.EqualTo(0)); + // This update should trigger an action with regards to the event queued after focus was regained. + // The one queued while out of focus should have been ignored and we should expect only one action triggered. + // Unless background behavior is set to IgnoreFocus in which case both events should trigger the action. + InputSystem.Update(InputUpdateType.Dynamic); + + actions = trace.ToArray(); + Assert.That(actions, Has.Length.EqualTo(backgroundBehavior == InputSettings.BackgroundBehavior.IgnoreFocus ? 2 : 1)); + Assert.That(actions[0].phase, Is.EqualTo(InputActionPhase.Performed)); + Vector2Control control = (Vector2Control)actions[0].control; + Assert.That(control.value, Is.EqualTo(new Vector2(1.0f, 2.0f)).Using(Vector2EqualityComparer.Instance)); + } + } + [Test] [Category("Actions")] public void Actions_TimeoutsDoNotGetTriggeredInEditorUpdates() diff --git a/Packages/com.unity.inputsystem/CHANGELOG.md b/Packages/com.unity.inputsystem/CHANGELOG.md index ce6fe06286..13a3c66807 100644 --- a/Packages/com.unity.inputsystem/CHANGELOG.md +++ b/Packages/com.unity.inputsystem/CHANGELOG.md @@ -28,6 +28,7 @@ however, it has to be formatted properly to pass verification tests. - Fixed the compilation warnings when used with Unity 6.4 (ISX-2349). - Fixed an issue where `InputSystemUIInputModule.localMultiPlayerRoot` could not be set to `null` when using `MultiplayerEventSystem`. [ISXB-1610](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1610) - Fixed an issue in `Keyboard` where the sub-script operator would return a `null` key control for the deprecated key `Key.IMESelected`. Now, an aliased `KeyControl`mapping to the IMESelected bit is returned for compability reasons. It is still strongly advised to not rely on this key since `IMESelected` bit isn't strictly a key and will be removed from the `Key` enumeration type in a future major revision. [ISXB-1541](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1541). +- An issue where a UITK MouseEvent was triggered when changing from Scene View to Game View in the Editor has been fixed. [ISXB-1671](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1671) ## [1.14.2] - 2025-08-05 diff --git a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs index 4926f14b08..eb6b0dedf6 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs @@ -2244,6 +2244,8 @@ internal struct AvailableDevice private bool m_NativeBeforeUpdateHooked; private bool m_HaveDevicesWithStateCallbackReceivers; private bool m_HasFocus; + private bool m_DiscardOutOfFocusEvents; + private double m_FocusRegainedTime; private InputEventStream m_InputEventStream; // We want to sync devices when the editor comes back into focus. Unfortunately, there's no @@ -3032,6 +3034,8 @@ internal void OnFocusChanged(bool focus) } else { + m_DiscardOutOfFocusEvents = true; + m_FocusRegainedTime = m_Runtime.currentTime; // On focus gain, reenable and sync devices. for (var i = 0; i < m_DevicesCount; ++i) { @@ -3201,50 +3205,18 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev var timesliceEvents = (updateType == InputUpdateType.Fixed || updateType == InputUpdateType.BeforeRender) && InputSystem.settings.updateMode == InputSettings.UpdateMode.ProcessEventsInFixedUpdate; - // Figure out if we can just flush the buffer and early out. - var canFlushBuffer = - false -#if UNITY_EDITOR - // If out of focus and runInBackground is off and ExactlyAsInPlayer is on, discard input. - || (!gameHasFocus && m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView && - (!m_Runtime.runInBackground || - m_Settings.backgroundBehavior == InputSettings.BackgroundBehavior.ResetAndDisableAllDevices)) -#else - || (!gameHasFocus && !m_Runtime.runInBackground) -#endif - ; - var canEarlyOut = - // Early out if there's no events to process. - eventBuffer.eventCount == 0 - || canFlushBuffer - -#if UNITY_EDITOR - // If we're in the background and not supposed to process events in this update (but somehow - // still ended up here), we're done. - || ((!gameHasFocus || gameShouldGetInputRegardlessOfFocus) && - ((m_Settings.backgroundBehavior == InputSettings.BackgroundBehavior.ResetAndDisableAllDevices && updateType != InputUpdateType.Editor) - || (m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode.AllDevicesRespectGameViewFocus && updateType != InputUpdateType.Editor) - || (m_Settings.backgroundBehavior == InputSettings.BackgroundBehavior.IgnoreFocus && m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView && updateType == InputUpdateType.Editor) - ) - // When the game is playing and has focus, we never process input in editor updates. All we - // do is just switch to editor state buffers and then exit. - || (gameIsPlaying && gameHasFocus && updateType == InputUpdateType.Editor)) -#endif - ; - + // Determine if we should flush the event buffer which would imply we exit early and do not process + // any of those events, ever. + var shouldFlushEventBuffer = ShouldFlushEventBuffer(); + // When we exit early, we may or may not flush the event buffer. It depends if we want to process events + // later once this method is called. + var shouldExitEarly = ShouldExitEarlyFromEventProcessing(eventBuffer, shouldFlushEventBuffer, updateType); #if UNITY_EDITOR - var dropStatusEvents = false; - if (!gameIsPlaying && gameShouldGetInputRegardlessOfFocus && (eventBuffer.sizeInBytes > (100 * 1024))) - { - // If the game is not playing but we're sending all input events to the game, the buffer can just grow unbounded. - // So, in that case, set a flag to say we'd like to drop status events, and do not early out. - canEarlyOut = false; - dropStatusEvents = true; - } + var dropStatusEvents = ShouldDropStatusEvents(eventBuffer, ref shouldExitEarly); #endif - if (canEarlyOut) + if (shouldExitEarly) { // Normally, we process action timeouts after first processing all events. If we have no // events, we still need to check timeouts. @@ -3253,7 +3225,7 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev k_InputUpdateProfilerMarker.End(); InvokeAfterUpdateCallback(updateType); - if (canFlushBuffer) + if (shouldFlushEventBuffer) eventBuffer.Reset(); m_CurrentUpdate = default; return; @@ -3317,25 +3289,9 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev continue; } -#endif - // In the editor, we discard all input events that occur in-between exiting edit mode and having - // entered play mode as otherwise we'll spill a bunch of UI events that have occurred while the - // UI was sort of neither in this mode nor in that mode. This would usually lead to the game receiving - // an accumulation of spurious inputs right in one of its first updates. - // - // NOTE: There's a chance the solution here will prove inadequate on the long run. We may do things - // here such as throwing partial touches away and then letting the rest of a touch go through. - // Could be that ultimately we need to issue a full reset of all devices at the beginning of - // play mode in the editor. -#if UNITY_EDITOR - if ((currentEventType == StateEvent.Type || - currentEventType == DeltaStateEvent.Type) && - (updateType & InputUpdateType.Editor) == 0 && - InputSystem.s_SystemObject.exitEditModeTime > 0 && - currentEventTimeInternal >= InputSystem.s_SystemObject.exitEditModeTime && - (currentEventTimeInternal < InputSystem.s_SystemObject.enterPlayModeTime || - InputSystem.s_SystemObject.enterPlayModeTime == 0)) + // Decide to skip events based on timing or focus state + if (ShouldDiscardEventInEditor(currentEventType, currentEventTimeInternal, updateType)) { m_InputEventStream.Advance(false); continue; @@ -3695,6 +3651,8 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev throw; } + m_DiscardOutOfFocusEvents = false; + if (shouldProcessActionTimeouts) ProcessStateChangeMonitorTimeouts(); @@ -3706,6 +3664,160 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev m_CurrentUpdate = default; } + /// + /// Determines if the event buffer should be flushed without processing events. + /// + /// True if the buffer should be flushed, false otherwise. + private bool ShouldFlushEventBuffer() + { +#if UNITY_EDITOR + // If out of focus and runInBackground is off and ExactlyAsInPlayer is on, discard input. + if (!gameHasFocus && + m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView + && + (!m_Runtime.runInBackground || m_Settings.backgroundBehavior == InputSettings.BackgroundBehavior.ResetAndDisableAllDevices)) + return true; +#else + // In player builds, flush if out of focus and not running in background + if (!gameHasFocus && !m_Runtime.runInBackground) + return true; +#endif + return false; + } + + /// + /// Determines if we should exit early from event processing without handling events. + /// + /// The current event buffer + /// Whether the buffer can be flushed + /// The current update type + /// True if we should exit early, false otherwise. + private bool ShouldExitEarlyFromEventProcessing(InputEventBuffer eventBuffer, bool canFlushBuffer, InputUpdateType updateType) + { + // Early out if there are no events to process + if (eventBuffer.eventCount == 0) + return true; + + // Early out if we can flush the buffer + if (canFlushBuffer) + return true; + +#if UNITY_EDITOR + // Check various PlayMode specific early exit conditions + if (ShouldExitEarlyInEditor(updateType)) + return true; + + // When the game is playing and has focus, we never process input in editor updates. + // All we do is just switch to editor state buffers and then exit. + if ((gameIsPlaying && gameHasFocus && updateType == InputUpdateType.Editor)) + return true; +#endif + + return false; + } + +#if UNITY_EDITOR + /// + /// Checks editor-specific conditions for early exit from event processing. + /// + /// The current update type + /// True if we should exit early in editor context, false otherwise. + /// + /// Whenever this method returns true, it usually means that events are left in the buffer and should be + /// processed in a next update call. + /// + private bool ShouldExitEarlyInEditor(InputUpdateType updateType) + { + // In Play Mode, if we're in the background and not supposed to process events in this update + if ((!gameHasFocus || gameShouldGetInputRegardlessOfFocus) && updateType != InputUpdateType.Editor) + { + if (m_Settings.backgroundBehavior == InputSettings.BackgroundBehavior.ResetAndDisableAllDevices || + m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode.AllDevicesRespectGameViewFocus) + return true; + } + + // Special case for IgnoreFocus behavior with AllDeviceInputAlwaysGoesToGameView in editor updates + if ((!gameHasFocus || gameShouldGetInputRegardlessOfFocus) && + m_Settings.backgroundBehavior == InputSettings.BackgroundBehavior.IgnoreFocus && + m_Settings.editorInputBehaviorInPlayMode == InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView && + updateType == InputUpdateType.Editor) + return true; + + return false; + } + + /// + /// Determines if status events should be dropped and modifies early exit behavior accordingly. + /// + /// The current event buffer + /// Reference to the early exit flag that may be modified + /// True if status events should be dropped, false otherwise. + private bool ShouldDropStatusEvents(InputEventBuffer eventBuffer, ref bool canEarlyOut) + { + // If the game is not playing but we're sending all input events to the game, + // the buffer can just grow unbounded. So, in that case, set a flag to say we'd + // like to drop status events, and do not early out. + if (!gameIsPlaying && gameShouldGetInputRegardlessOfFocus && (eventBuffer.sizeInBytes > (100 * 1024))) + { + canEarlyOut = false; + return true; + } + return false; + } + + /// + /// Determines if an event should be discarded based on timing or focus state. + /// + /// The type of the current event + /// The internal time of the current event + /// The current update type + /// True if the event should be discarded, false otherwise. + private bool ShouldDiscardEventInEditor(FourCC eventType, double eventTime, InputUpdateType updateType) + { + // Check if this is an event that occurred during edit mode transition + if (ShouldDiscardEditModeTransitionEvent(eventType, eventTime, updateType)) + return true; + + // Check if this is an out-of-focus event that should be discarded + if (ShouldDiscardOutOfFocusEvent(eventTime)) + return true; + + return false; + } + + /// + /// In the editor, we discard all input events that occur in-between exiting edit mode and having + /// entered play mode as otherwise we'll spill a bunch of UI events that have occurred while the + /// UI was sort of neither in this mode nor in that mode. This would usually lead to the game receiving + /// an accumulation of spurious inputs right in one of its first updates. + /// + /// NOTE: There's a chance the solution here will prove inadequate on the long run. We may do things + /// here such as throwing partial touches away and then letting the rest of a touch go through. + /// Could be that ultimately we need to issue a full reset of all devices at the beginning of + /// play mode in the editor. + /// + private bool ShouldDiscardEditModeTransitionEvent(FourCC eventType, double eventTime, InputUpdateType updateType) + { + return (eventType == StateEvent.Type || eventType == DeltaStateEvent.Type) && + (updateType & InputUpdateType.Editor) == 0 && + InputSystem.s_SystemObject.exitEditModeTime > 0 && + eventTime >= InputSystem.s_SystemObject.exitEditModeTime && + (eventTime < InputSystem.s_SystemObject.enterPlayModeTime || + InputSystem.s_SystemObject.enterPlayModeTime == 0); + } + + /// + /// Checks if an event should be discarded because it occurred while out of focus, under specific settings. + /// + private bool ShouldDiscardOutOfFocusEvent(double eventTime) + { + if (gameHasFocus && m_Settings.backgroundBehavior != InputSettings.BackgroundBehavior.IgnoreFocus) + return m_DiscardOutOfFocusEvents && eventTime < m_FocusRegainedTime; + return false; + } + +#endif + bool AreMaximumEventBytesPerUpdateExceeded(uint totalEventBytesProcessed) { if (m_Settings.maxEventBytesPerUpdate > 0 &&