-
Notifications
You must be signed in to change notification settings - Fork 328
FIX: MouseDownEvent is triggered when changing from Scene view to Game view (ISXB-1671) #2234
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
6157498
300896c
f78e947
f1ed615
9023b74
510730e
ac75141
6fb0bb3
8a5b40c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -642,6 +642,56 @@ public void Actions_DoNotGetTriggeredByEditorUpdates() | |
| } | ||
| } | ||
|
|
||
| [Test] | ||
| [Category("Actions")] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still need to add a InputForUI specific test for this. |
||
| [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<Mouse>(); | ||
| var mousePointAction = new InputAction(binding: "<Mouse>/position", type: InputActionType.PassThrough); | ||
| mousePointAction.Enable(); | ||
|
|
||
| using (var trace = new InputActionTrace(mousePointAction)) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would recommend letting the time step parameter also be an input parameter to the test case. It should still be valid for timeStep = 0.0f, since our logic need to handle event order as the determining factor when time isn't sufficient to tell them apart. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So I would recommend adding that test as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In that case, this would not work. Time needs to move; otherwise, we can't know what events have occurred before and after the focus. This solution relies on event timestamps. Similar to what we do for transitions between Edit Mode and Entering Play Mode, see the conditions in In theory, if we had focus events in the queues, we wouldn't need to know about timestamps. However, as we discussed, there is a higher risk in introducing such a change. We should still conduct an exploration of the implementation, but I would prefer to land first a less risky solution for this particular bug. I'm pappy to prototype a solution together if you like, after this fix. We can sync on Slack. |
||
| { | ||
| 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; | ||
| // Make sure the value is from the event after focus was regained. | ||
| Assert.That(control.value, Is.EqualTo(new Vector2(1.0f, 2.0f)).Using(Vector2EqualityComparer.Instance)); | ||
| } | ||
| } | ||
|
|
||
| [Test] | ||
| [Category("Actions")] | ||
| public void Actions_TimeoutsDoNotGetTriggeredInEditorUpdates() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2244,6 +2244,8 @@ | |
| private bool m_NativeBeforeUpdateHooked; | ||
| private bool m_HaveDevicesWithStateCallbackReceivers; | ||
| private bool m_HasFocus; | ||
| private bool m_DiscardOutOfFocusEvents; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A bit of a corner case, but what happens with these if there is a domain reload while in playmode? Will they be reinitialised to the same values they had before the domain reload was initiated? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, good question. How can I trigger a domain reload in play mode so I can test that? |
||
| 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 @@ | |
| } | ||
| 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,20 @@ | |
| 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 = | ||
| eventBuffer.eventCount == 0 || shouldFlushEventBuffer || ShouldExitEarlyFromEventProcessing(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 +3227,7 @@ | |
|
|
||
| k_InputUpdateProfilerMarker.End(); | ||
| InvokeAfterUpdateCallback(updateType); | ||
| if (canFlushBuffer) | ||
| if (shouldFlushEventBuffer) | ||
| eventBuffer.Reset(); | ||
| m_CurrentUpdate = default; | ||
| return; | ||
|
|
@@ -3317,25 +3291,9 @@ | |
|
|
||
| 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 +3653,8 @@ | |
| throw; | ||
| } | ||
|
|
||
| m_DiscardOutOfFocusEvents = false; | ||
|
|
||
| if (shouldProcessActionTimeouts) | ||
| ProcessStateChangeMonitorTimeouts(); | ||
|
|
||
|
|
@@ -3706,6 +3666,153 @@ | |
| m_CurrentUpdate = default; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Determines if the event buffer should be flushed without processing events. | ||
| /// </summary> | ||
| /// <returns>True if the buffer should be flushed, false otherwise.</returns> | ||
| 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; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Determines if we should exit early from event processing without handling events. | ||
| /// </summary> | ||
| /// <param name="eventBuffer">The current event buffer</param> | ||
| /// <param name="canFlushBuffer">Whether the buffer can be flushed</param> | ||
| /// <param name="updateType">The current update type</param> | ||
| /// <returns>True if we should exit early, false otherwise.</returns> | ||
| private bool ShouldExitEarlyFromEventProcessing(InputUpdateType updateType) | ||
| { | ||
| #if UNITY_EDITOR | ||
| // Check various PlayMode specific early exit conditions | ||
| if (ShouldExitEarlyBasedOnBackgroundBehavior(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 | ||
| /// <summary> | ||
| /// Checks background behavior conditions for early exit from event processing. | ||
| /// </summary> | ||
| /// <param name="updateType">The current update type</param> | ||
| /// <returns>True if we should exit early, false otherwise.</returns> | ||
| /// <remarks> | ||
| /// Whenever this method returns true, it usually means that events are left in the buffer and should be | ||
| /// processed in a next update call. | ||
| /// </remarks> | ||
| private bool ShouldExitEarlyBasedOnBackgroundBehavior(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; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Determines if status events should be dropped and modifies early exit behavior accordingly. | ||
| /// </summary> | ||
| /// <param name="eventBuffer">The current event buffer</param> | ||
| /// <param name="canEarlyOut">Reference to the early exit flag that may be modified</param> | ||
| /// <returns>True if status events should be dropped, false otherwise.</returns> | ||
| 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; | ||
| } | ||
|
|
||
| /// <summary> | ||
jfreire-unity marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| /// Determines if an event should be discarded based on timing or focus state. | ||
| /// </summary> | ||
| /// <param name="eventType">The type of event</param> | ||
| /// <param name="eventTime">The internal time of the current event</param> | ||
| /// <param name="updateType">The current update type</param> | ||
| /// <returns>True if the event should be discarded, false otherwise.</returns> | ||
| 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; | ||
| } | ||
|
|
||
| /// <summary> | ||
jfreire-unity marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| /// 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. | ||
| /// </summary> | ||
| 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); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Checks if an event should be discarded because it occurred while out of focus, under specific settings. | ||
| /// </summary> | ||
| private bool ShouldDiscardOutOfFocusEvent(double eventTime) | ||
| { | ||
| // If we care about focus, check if the event occurred while out of focus based on its timestamp. | ||
| 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 && | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.