From 61574984776fdb6824c4a6d0fa50a816e7349336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freire?= Date: Thu, 11 Sep 2025 14:07:19 +0300 Subject: [PATCH 1/6] Discard events received while out focus --- .../InputSystem/InputManager.cs | 79 +++++++++++++++---- 1 file changed, 63 insertions(+), 16 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs index 4926f14b08..6dac79b541 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) { @@ -3319,23 +3323,10 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev } #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 +3686,8 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev throw; } + m_DiscardOutOfFocusEvents = false; + if (shouldProcessActionTimeouts) ProcessStateChangeMonitorTimeouts(); @@ -3706,6 +3699,60 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev m_CurrentUpdate = default; } +#if UNITY_EDITOR + /// + /// 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 && From 300896c9f8452c9cbd48d05d349cfa6a48a29ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freire?= Date: Thu, 11 Sep 2025 16:16:22 +0300 Subject: [PATCH 2/6] Add Input Action tests to validate the change It's tested against all background behavior settings. --- Assets/Tests/InputSystem/CoreTests_Actions.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) 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() From f78e9477e12774d35e527799fb18b6909c5fb11f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freire?= Date: Thu, 11 Sep 2025 16:23:06 +0300 Subject: [PATCH 3/6] Refactor some conditions to improve readability The goal is here is to make those conditions self contained in "chunks" so that we can slightly reduce the cognitive load when reading through the OnUpdate() function. This is a personal preference and others in the team might disagree with these changes. --- .../InputSystem/InputManager.cs | 150 +++++++++++++----- 1 file changed, 109 insertions(+), 41 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs index 6dac79b541..e7b2044c87 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs @@ -3205,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 + // 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 - // 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)) + var dropStatusEvents = ShouldDropStatusEvents(eventBuffer, ref shouldExitEarly); #endif - ; - -#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; - } -#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. @@ -3257,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; @@ -3699,7 +3667,107 @@ 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. /// From f1ed615bf5aa5ac37875483b515cc67dc25c1804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freire?= Date: Thu, 11 Sep 2025 16:55:11 +0300 Subject: [PATCH 4/6] Merge UNITY_EDITOR defines --- Packages/com.unity.inputsystem/InputSystem/InputManager.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs index e7b2044c87..eb6b0dedf6 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs @@ -3289,10 +3289,7 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev continue; } -#endif - -#if UNITY_EDITOR // Decide to skip events based on timing or focus state if (ShouldDiscardEventInEditor(currentEventType, currentEventTimeInternal, updateType)) { From 9023b742de16d1eefe1df9fa4e9fe38e0b30853a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freire?= Date: Fri, 12 Sep 2025 14:35:51 +0300 Subject: [PATCH 5/6] Update CHANGELOG --- Packages/com.unity.inputsystem/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Packages/com.unity.inputsystem/CHANGELOG.md b/Packages/com.unity.inputsystem/CHANGELOG.md index 48b062ee13..18f8d50e55 100644 --- a/Packages/com.unity.inputsystem/CHANGELOG.md +++ b/Packages/com.unity.inputsystem/CHANGELOG.md @@ -26,6 +26,7 @@ however, it has to be formatted properly to pass verification tests. - Fixed an issue in `GamepadIconExample` which resulted in icons for left and right triggers not being displayed after a rebind to the exact same controls. ISXB-1593. - 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 From 510730e3e96cb7138176944b5f88044ce2b8be93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freire?= Date: Fri, 12 Sep 2025 14:43:11 +0300 Subject: [PATCH 6/6] Remove line for after merge actions regarding ports in PR template --- .github/pull_request_template.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) 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.