Skip to content

Commit 0cded04

Browse files
author
Rene Damm
authored
FIX: Input in editor leaking into play mode (case 1191342, #978).
1 parent b824b60 commit 0cded04

File tree

8 files changed

+78
-9
lines changed

8 files changed

+78
-9
lines changed

Assets/Tests/InputSystem/CoreTests_Editor.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2150,6 +2150,36 @@ public IEnumerator TODO_Editor_PlayerActionDoesNotTriggerWhenGameViewIsNotFocuse
21502150
throw new NotImplementedException();
21512151
}
21522152

2153+
// While going into play mode, the editor will be unresponsive. So what will happen is that when the user clicks the
2154+
// play mode button and then moves the mouse around while Unity is busy going into play mode, the game will receive
2155+
// a huge pointer motion delta in one of its first frames. If pointer motion is tied to camera motion, for example,
2156+
// this will usually lead to the camera looking down at the ground because after clicking the play mode button at the
2157+
// top of the UI, the user will likely move the pointer down towards the game view area thus generating a large down
2158+
// motion delta.
2159+
//
2160+
// What we do to counter this is to record the time of when we enter play mode and then record the time again when
2161+
// we have fully entered play mode. All the events in-between we discard.
2162+
[Test]
2163+
[Category("Editor")]
2164+
public void Editor_InputEventsOccurringWhileGoingIntoPlayMode_AreDiscarded()
2165+
{
2166+
var mouse = InputSystem.AddDevice<Mouse>();
2167+
2168+
// We need to actually pass time and have a non-zero start time for this to work.
2169+
currentTime = 1;
2170+
InputSystem.OnPlayModeChange(PlayModeStateChange.ExitingEditMode);
2171+
InputSystem.QueueStateEvent(mouse, new MouseState { position = new Vector2(234, 345) });
2172+
currentTime = 2;
2173+
InputSystem.OnPlayModeChange(PlayModeStateChange.EnteredPlayMode);
2174+
2175+
InputSystem.Update();
2176+
2177+
Assert.That(mouse.position.ReadValue(), Is.EqualTo(default(Vector2)));
2178+
2179+
// Make sure the event was not left in the buffer.
2180+
Assert.That(runtime.m_EventCount, Is.EqualTo(0));
2181+
}
2182+
21532183
////TODO: tests for InputAssetImporter; for this we need C# mocks to be able to cut us off from the actual asset DB
21542184
}
21552185
#endif // UNITY_EDITOR

Packages/com.unity.inputsystem/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ however, it has to be formatted properly to pass verification tests.
3434

3535
- `InputUser` in combination with touchscreens no longer throws `InvalidOperationException` complaining about incorrect state format.
3636
* In a related change, `InputControlExtensions.GetStatePtrFromStateEvent` now works with touch events, too.
37+
- Input that occurs in-between pressing the play button and the game starting no longer leaks into the game (case 1191342).
38+
* This usually manifested itself as large accumulated mouse deltas leading to such effects as the camera immediately jerking around on game start.
3739
- Removing a device no longer has the potential of corrupting state change monitors (and thus actions getting triggered) from other devices.
3840
* This bug led to input being missed on a device once another device had been removed.
3941

Packages/com.unity.inputsystem/InputSystem/Actions/Interactions/MultiTapInteraction.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using UnityEngine.InputSystem.Editor;
44
#endif
55

6+
////TODO: change this so that the interaction stays performed when the tap count is reached until the button is released
7+
68
namespace UnityEngine.InputSystem.Interactions
79
{
810
/// <summary>

Packages/com.unity.inputsystem/InputSystem/InputManager.cs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2477,16 +2477,40 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev
24772477
if (remainingEventCount == 0)
24782478
break;
24792479

2480+
var currentEventTimeInternal = currentEventReadPtr->internalTime;
2481+
2482+
// In the editor, we discard all input events that occur in-between exiting edit mode and having
2483+
// entered play mode as otherwise we'll spill a bunch of UI events that have occurred while the
2484+
// UI was sort of neither in this mode nor in that mode. This would usually lead to the game receiving
2485+
// an accumulation of spurious inputs right in one of its first updates.
2486+
//
2487+
// NOTE: There's a chance the solution here will prove inadequate on the long run. We may do things
2488+
// here such as throwing partial touches away and then letting the rest of a touch go through.
2489+
// Could be that ultimately we need to issue a full reset of all devices at the beginning of
2490+
// play mode in the editor.
2491+
#if UNITY_EDITOR
2492+
if ((updateType & InputUpdateType.Editor) == 0 &&
2493+
InputSystem.s_SystemObject.exitEditModeTime > 0 &&
2494+
currentEventTimeInternal >= InputSystem.s_SystemObject.exitEditModeTime &&
2495+
(currentEventTimeInternal < InputSystem.s_SystemObject.enterPlayModeTime ||
2496+
InputSystem.s_SystemObject.enterPlayModeTime == 0))
2497+
{
2498+
eventBuffer.AdvanceToNextEvent(ref currentEventReadPtr, ref currentEventWritePtr,
2499+
ref numEventsRetainedInBuffer, ref remainingEventCount, leaveEventInBuffer: false);
2500+
continue;
2501+
}
2502+
#endif
2503+
24802504
// If we're timeslicing, check if the event time is within limits.
2481-
if (timesliceEvents && currentEventReadPtr->internalTime >= currentTime)
2505+
if (timesliceEvents && currentEventTimeInternal >= currentTime)
24822506
{
24832507
eventBuffer.AdvanceToNextEvent(ref currentEventReadPtr, ref currentEventWritePtr,
24842508
ref numEventsRetainedInBuffer, ref remainingEventCount, leaveEventInBuffer: true);
24852509
continue;
24862510
}
24872511

2488-
if (currentEventReadPtr->internalTime <= currentTime)
2489-
totalEventLag += currentTime - currentEventReadPtr->internalTime;
2512+
if (currentEventTimeInternal <= currentTime)
2513+
totalEventLag += currentTime - currentEventTimeInternal;
24902514

24912515
// Grab device for event. In before-render updates, we already had to
24922516
// check the device.
@@ -2507,10 +2531,9 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev
25072531
}
25082532

25092533
// Give listeners a shot at the event.
2510-
var listenerCount = m_EventListeners.length;
2511-
if (listenerCount > 0)
2534+
if (m_EventListeners.length > 0)
25122535
{
2513-
for (var i = 0; i < listenerCount; ++i)
2536+
for (var i = 0; i < m_EventListeners.length; ++i)
25142537
m_EventListeners[i](new InputEventPtr(currentEventReadPtr), device);
25152538

25162539
// If a listener marks the event as handled, we don't process it further.
@@ -2524,7 +2547,6 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev
25242547

25252548
// Process.
25262549
var currentEventType = currentEventReadPtr->type;
2527-
var currentEventTimeInternal = currentEventReadPtr->internalTime;
25282550
switch (currentEventType)
25292551
{
25302552
case StateEvent.Type:

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2801,12 +2801,18 @@ internal static void InitializeInEditor(IInputRuntime runtime = null)
28012801
Profiling.Profiler.EndSample();
28022802
}
28032803

2804-
private static void OnPlayModeChange(PlayModeStateChange change)
2804+
internal static void OnPlayModeChange(PlayModeStateChange change)
28052805
{
28062806
switch (change)
28072807
{
28082808
case PlayModeStateChange.ExitingEditMode:
28092809
s_SystemObject.settings = JsonUtility.ToJson(settings);
2810+
s_SystemObject.exitEditModeTime = InputRuntime.s_Instance.currentTime;
2811+
s_SystemObject.enterPlayModeTime = 0;
2812+
break;
2813+
2814+
case PlayModeStateChange.EnteredPlayMode:
2815+
s_SystemObject.enterPlayModeTime = InputRuntime.s_Instance.currentTime;
28102816
break;
28112817

28122818
////TODO: also nuke all callbacks installed on InputActions and InputActionMaps
@@ -3028,6 +3034,7 @@ internal struct State
30283034
[SerializeField] public InputRemoting.SerializedState remotingState;
30293035
#if UNITY_EDITOR
30303036
[SerializeField] public InputEditorUserSettings.SerializedState userSettings;
3037+
[SerializeField] public string systemObject;
30313038
#endif
30323039
////REVIEW: preserve InputUser state? (if even possible)
30333040
}
@@ -3064,6 +3071,7 @@ internal static void SaveAndReset(bool enableRemoting = false, IInputRuntime run
30643071
remotingState = s_Remote?.SaveState() ?? new InputRemoting.SerializedState(),
30653072
#if UNITY_EDITOR
30663073
userSettings = InputEditorUserSettings.s_Settings,
3074+
systemObject = JsonUtility.ToJson(s_SystemObject),
30673075
#endif
30683076
});
30693077

@@ -3094,6 +3102,7 @@ internal static void Restore()
30943102

30953103
#if UNITY_EDITOR
30963104
InputEditorUserSettings.s_Settings = state.userSettings;
3105+
JsonUtility.FromJsonOverwrite(state.systemObject, s_SystemObject);
30973106
#endif
30983107

30993108
// Get devices that keep global lists (like Gamepad) to re-initialize them

Packages/com.unity.inputsystem/InputSystem/InputSystemObject.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ internal class InputSystemObject : ScriptableObject, ISerializationCallbackRecei
1515
[SerializeField] public InputSystem.State systemState;
1616
[SerializeField] public bool newInputBackendsCheckedAsEnabled;
1717
[SerializeField] public string settings;
18+
[SerializeField] public double exitEditModeTime;
19+
[SerializeField] public double enterPlayModeTime;
1820

1921
public void OnBeforeSerialize()
2022
{

Packages/com.unity.inputsystem/InputSystem/Plugins/OnScreen/OnScreenControl.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
////TODO: make this survive domain reloads
1010

11+
////TODO: allow feeding into more than one control
12+
1113
namespace UnityEngine.InputSystem.OnScreen
1214
{
1315
/// <summary>

Packages/com.unity.inputsystem/Tests/TestFixture/InputTestRuntime.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ public double currentTimeOffsetToRealtimeSinceStartup
359359

360360
private int m_NextDeviceId = 1;
361361
private int m_NextEventId = 1;
362-
private int m_EventCount;
362+
internal int m_EventCount;
363363
private int m_EventWritePosition;
364364
private NativeArray<byte> m_EventBuffer = new NativeArray<byte>(1024 * 1024, Allocator.Persistent);
365365
private List<PairedUser> m_UserPairings;

0 commit comments

Comments
 (0)