Skip to content

Commit 3d0dcad

Browse files
authored
FIX: Swapping InputManager state buffers and update step counts back to latest player update after editor update (#1424)
1 parent e270c16 commit 3d0dcad

File tree

9 files changed

+165
-31
lines changed

9 files changed

+165
-31
lines changed

Assets/Tests/InputSystem/CoreTests_Editor.cs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,6 @@ public void Editor_WhenPlaying_EditorUpdatesKeepSeparateStateFromPlayerUpdates()
350350

351351
InputSystem.QueueStateEvent(gamepad, new GamepadState {leftTrigger = 0.25f});
352352
InputSystem.Update(InputUpdateType.Dynamic);
353-
354353
Assert.That(gamepad.leftTrigger.ReadValue(), Is.EqualTo(0.25).Within(0.000001));
355354
Assert.That(gamepad.leftTrigger.ReadValueFromPreviousFrame(), Is.Zero.Within(0.000001));
356355

@@ -359,13 +358,50 @@ public void Editor_WhenPlaying_EditorUpdatesKeepSeparateStateFromPlayerUpdates()
359358
// started with.
360359
InputSystem.QueueStateEvent(gamepad, new GamepadState {leftTrigger = 0.75f});
361360
InputSystem.Update(InputUpdateType.Editor);
362-
363361
Assert.That(gamepad.leftTrigger.ReadValue(), Is.Zero.Within(0.000001));
364362
Assert.That(gamepad.leftTrigger.ReadValueFromPreviousFrame(), Is.Zero.Within(0.000001));
365363

366364
// So running a player update now should make the input come through in player state.
367365
InputSystem.Update(InputUpdateType.Dynamic);
366+
Assert.That(gamepad.leftTrigger.ReadValue(), Is.EqualTo(0.75).Within(0.000001));
367+
Assert.That(gamepad.leftTrigger.ReadValueFromPreviousFrame(), Is.EqualTo(0.25).Within(0.000001));
368+
}
368369

370+
[Test]
371+
[Category("Editor")]
372+
// Case 1368559
373+
// Case 1367556
374+
// Case 1372830
375+
public void Editor_WhenPlaying_ItsPossibleToQueryPlayerStateAfterEditorUpdate()
376+
{
377+
InputSystem.settings.editorInputBehaviorInPlayMode = default;
378+
379+
var gamepad = InputSystem.AddDevice<Gamepad>();
380+
381+
// ----------------- Engine frame 1
382+
383+
InputSystem.QueueStateEvent(gamepad, new GamepadState {leftTrigger = 0.25f});
384+
InputSystem.Update(InputUpdateType.Dynamic);
385+
Assert.That(gamepad.leftTrigger.ReadValue(), Is.EqualTo(0.25).Within(0.000001));
386+
Assert.That(gamepad.leftTrigger.ReadValueFromPreviousFrame(), Is.Zero.Within(0.000001));
387+
388+
InputSystem.QueueStateEvent(gamepad, new GamepadState {leftTrigger = 0.75f});
389+
InputSystem.Update(InputUpdateType.Editor);
390+
Assert.That(gamepad.leftTrigger.ReadValue(), Is.Zero.Within(0.000001));
391+
Assert.That(gamepad.leftTrigger.ReadValueFromPreviousFrame(), Is.Zero.Within(0.000001));
392+
393+
// ----------------- Engine frame 2
394+
395+
// Simulate early player loop callback
396+
runtime.onPlayerLoopInitialization();
397+
398+
// This code might be running in EarlyUpdate or FixedUpdate, _before_ Dynamic update is invoked.
399+
// We should read values from last player update, meaning we report values from last frame.
400+
Assert.That(gamepad.leftTrigger.ReadValue(), Is.EqualTo(0.25).Within(0.000001));
401+
Assert.That(gamepad.leftTrigger.ReadValueFromPreviousFrame(), Is.Zero.Within(0.000001));
402+
403+
// Running a player update now should make the input come through in player state.
404+
InputSystem.Update(InputUpdateType.Dynamic);
369405
Assert.That(gamepad.leftTrigger.ReadValue(), Is.EqualTo(0.75).Within(0.000001));
370406
Assert.That(gamepad.leftTrigger.ReadValueFromPreviousFrame(), Is.EqualTo(0.25).Within(0.000001));
371407
}

Assets/Tests/InputSystem/CoreTests_Events.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ public unsafe void Events_InFixedUpdate_AreTimesliced()
490490
Assert.That(receivedEvents[2].time, Is.EqualTo(2.9).Within(0.00001));
491491
Assert.That(gamepad.leftTrigger.ReadValue(), Is.EqualTo(0.3456).Within(0.00001));
492492

493-
Assert.That(InputUpdate.s_LastUpdateRetainedEventCount, Is.Zero);
493+
Assert.That(runtime.eventCount, Is.Zero);
494494

495495
receivedEvents.Clear();
496496

@@ -509,7 +509,7 @@ public unsafe void Events_InFixedUpdate_AreTimesliced()
509509
Assert.That(receivedEvents[1].time, Is.EqualTo(3 + 0.002).Within(0.00001));
510510
Assert.That(gamepad.leftTrigger.ReadValue(), Is.EqualTo(0.2345).Within(0.00001));
511511

512-
Assert.That(InputUpdate.s_LastUpdateRetainedEventCount, Is.EqualTo(2));
512+
Assert.That(runtime.eventCount, Is.EqualTo(2));
513513

514514
receivedEvents.Clear();
515515

@@ -521,7 +521,7 @@ public unsafe void Events_InFixedUpdate_AreTimesliced()
521521
Assert.That(receivedEvents[0].time, Is.EqualTo(3 + 1.0 / 60 + 0.001).Within(0.00001));
522522
Assert.That(gamepad.leftTrigger.ReadValue(), Is.EqualTo(0.3456).Within(0.00001));
523523

524-
Assert.That(InputUpdate.s_LastUpdateRetainedEventCount, Is.EqualTo(1));
524+
Assert.That(runtime.eventCount, Is.EqualTo(1));
525525

526526
receivedEvents.Clear();
527527

@@ -533,7 +533,7 @@ public unsafe void Events_InFixedUpdate_AreTimesliced()
533533
Assert.That(receivedEvents[0].time, Is.EqualTo(3 + 2 * (1.0 / 60) + 0.001).Within(0.00001));
534534
Assert.That(gamepad.leftTrigger.ReadValue(), Is.EqualTo(0.4567).Within(0.00001));
535535

536-
Assert.That(InputUpdate.s_LastUpdateRetainedEventCount, Is.Zero);
536+
Assert.That(runtime.eventCount, Is.Zero);
537537

538538
receivedEvents.Clear();
539539

@@ -544,7 +544,7 @@ public unsafe void Events_InFixedUpdate_AreTimesliced()
544544
Assert.That(receivedEvents, Has.Count.Zero);
545545
Assert.That(gamepad.leftTrigger.ReadValue(), Is.EqualTo(0.4567).Within(0.00001));
546546

547-
Assert.That(InputUpdate.s_LastUpdateRetainedEventCount, Is.Zero);
547+
Assert.That(runtime.eventCount, Is.Zero);
548548
}
549549

550550
[Test]

Packages/com.unity.inputsystem/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ however, it has to be formatted properly to pass verification tests.
2626
- Generic gamepad short display button names where incorrectly mapped on Switch (`A` instead of `B`, etc).
2727
- Fixed an issue where resetting an action via `InputAction.Reset()` while being in disabled state would prevent the action from being enabled again. ([case 1370732](https://issuetracker.unity3d.com/product/unity/issues/guid/1370732/)).
2828
- Fixed "Default constructor not found for type UnityEngine.InputSystem.iOS.LowLevel.iOSStepCounter" any other potential exceptions due to classes, methods, fields and properties being stripped when managed stripping setting set to medium or high ([case 1368761](https://issuetracker.unity3d.com/issues/ios-new-input-system-iosstepcounter-crash-on-launch-with-managed-stripping)).
29+
- Fixed `action.ReadValue` and others returning invalid data when used from `FixedUpdate` or early update when running in play mode in the editor ([case 1368559](https://issuetracker.unity3d.com/issues/enter-key-is-not-registered-when-using-waspressedthisframe-with-input-system-1-dot-1-1) [case 1367556](https://issuetracker.unity3d.com/issues/input-action-readvalue-always-returns-zero-when-called-from-fixedupdate) [case 1372830](https://issuetracker.unity3d.com/issues/querying-inputs-before-preupdate-dot-newinputupdate-returns-invalid-data-when-running-in-play-mode-in-editor)).
2930

3031
### Added
3132

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ internal unsafe interface IInputRuntime
8989

9090
Func<InputUpdateType, bool> onShouldRunUpdate { get; set; }
9191

92+
#if UNITY_EDITOR
93+
/// <summary>
94+
/// Set delegate to be called during player loop initialization callbacks.
95+
/// </summary>
96+
Action onPlayerLoopInitialization { get; set; }
97+
#endif
98+
9299
/// <summary>
93100
/// Set delegate to be called when a new device is discovered.
94101
/// </summary>

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

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1901,13 +1901,19 @@ internal void InstallRuntime(IInputRuntime runtime)
19011901
m_Runtime.onDeviceDiscovered = null;
19021902
m_Runtime.onPlayerFocusChanged = null;
19031903
m_Runtime.onShouldRunUpdate = null;
1904+
#if UNITY_EDITOR
1905+
m_Runtime.onPlayerLoopInitialization = null;
1906+
#endif
19041907
}
19051908

19061909
m_Runtime = runtime;
19071910
m_Runtime.onUpdate = OnUpdate;
19081911
m_Runtime.onDeviceDiscovered = OnNativeDeviceDiscovered;
19091912
m_Runtime.onPlayerFocusChanged = OnFocusChanged;
19101913
m_Runtime.onShouldRunUpdate = ShouldRunUpdate;
1914+
#if UNITY_EDITOR
1915+
m_Runtime.onPlayerLoopInitialization = OnPlayerLoopInitialization;
1916+
#endif
19111917
m_Runtime.pollingFrequency = pollingFrequency;
19121918
m_HasFocus = m_Runtime.isPlayerFocused;
19131919

@@ -2011,6 +2017,11 @@ internal struct AvailableDevice
20112017
private InputUpdateType m_CurrentUpdate;
20122018
internal InputStateBuffers m_StateBuffers;
20132019

2020+
#if UNITY_EDITOR
2021+
// remember time offset to correctly restore it after editor mode is done
2022+
private double latestNonEditorTimeOffsetToRealtimeSinceStartup;
2023+
#endif
2024+
20142025
// We don't use UnityEvents and thus don't persist the callbacks during domain reloads.
20152026
// Restoration of UnityActions is unreliable and it's too easy to end up with double
20162027
// registrations what will lead to all kinds of misbehavior.
@@ -2211,7 +2222,7 @@ private unsafe void ReallocateStateBuffers()
22112222

22122223
// Switch to buffers.
22132224
InputStateBuffers.SwitchTo(m_StateBuffers,
2214-
InputUpdate.s_LastUpdateType != InputUpdateType.None ? InputUpdate.s_LastUpdateType : defaultUpdateType);
2225+
InputUpdate.s_LatestUpdateType != InputUpdateType.None ? InputUpdate.s_LatestUpdateType : defaultUpdateType);
22152226

22162227
////TODO: need to update state change monitors
22172228
}
@@ -2829,6 +2840,18 @@ internal void LeavePlayMode()
28292840
m_CurrentUpdate = default;
28302841
}
28312842

2843+
private void OnPlayerLoopInitialization()
2844+
{
2845+
if (!gameIsPlaying || // if game is not playing
2846+
!InputUpdate.s_LatestUpdateType.IsEditorUpdate() || // or last update was not editor update
2847+
!InputUpdate.s_LatestNonEditorUpdateType.IsPlayerUpdate()) // or update before that was not player update
2848+
return; // then no need to restore anything
2849+
2850+
InputUpdate.RestoreStateAfterEditorUpdate();
2851+
InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup = latestNonEditorTimeOffsetToRealtimeSinceStartup;
2852+
InputStateBuffers.SwitchTo(m_StateBuffers, InputUpdate.s_LatestUpdateType);
2853+
}
2854+
28322855
#endif
28332856

28342857
internal bool ShouldRunUpdate(InputUpdateType updateType)
@@ -2908,8 +2931,13 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev
29082931
// Update metrics.
29092932
++m_Metrics.totalUpdateCount;
29102933

2911-
InputUpdate.s_LastUpdateRetainedEventCount = 0;
2912-
InputUpdate.s_LastUpdateRetainedEventBytes = 0;
2934+
#if UNITY_EDITOR
2935+
// If current update is editor update and previous update was non-editor,
2936+
// store the time offset so we can restore it right after editor update is complete
2937+
if (((updateType & InputUpdateType.Editor) == InputUpdateType.Editor) && (m_CurrentUpdate & InputUpdateType.Editor) == 0)
2938+
latestNonEditorTimeOffsetToRealtimeSinceStartup =
2939+
InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup;
2940+
#endif
29132941

29142942
// Store current time offset.
29152943
InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup = m_Runtime.currentTimeOffsetToRealtimeSinceStartup;
@@ -3368,11 +3396,6 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev
33683396
((double)(Stopwatch.GetTimestamp() - processingStartTime)) / Stopwatch.Frequency;
33693397
m_Metrics.totalEventLagTime += totalEventLag;
33703398

3371-
// Remember how much data we retained so that we don't count it against the next
3372-
// batch of events that we receive.
3373-
InputUpdate.s_LastUpdateRetainedEventCount = (uint)m_InputEventStream.numEventsRetainedInBuffer;
3374-
InputUpdate.s_LastUpdateRetainedEventBytes = m_InputEventStream.numBytesRetainedInBuffer;
3375-
33763399
m_InputEventStream.Close(ref eventBuffer);
33773400
}
33783401
catch (Exception)

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

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,12 @@ public enum InputUpdateType
7171
internal static class InputUpdate
7272
{
7373
public static uint s_UpdateStepCount; // read only, but kept as a variable for performance reasons
74-
public static InputUpdateType s_LastUpdateType;
74+
public static InputUpdateType s_LatestUpdateType;
7575
public static UpdateStepCount s_PlayerUpdateStepCount;
7676
#if UNITY_EDITOR
77+
public static InputUpdateType s_LatestNonEditorUpdateType;
7778
public static UpdateStepCount s_EditorUpdateStepCount;
7879
#endif
79-
public static uint s_LastUpdateRetainedEventBytes;
80-
public static uint s_LastUpdateRetainedEventCount;
8180

8281
[Serializable]
8382
public struct UpdateStepCount
@@ -107,22 +106,24 @@ public struct SerializedState
107106
public InputUpdateType lastUpdateType;
108107
public UpdateStepCount playerUpdateStepCount;
109108
#if UNITY_EDITOR
109+
public InputUpdateType lastNonEditorUpdateType;
110110
public UpdateStepCount editorUpdateStepCount;
111111
#endif
112-
public uint lastUpdateRetainedEventBytes;
113-
public uint lastUpdateRetainedEventCount;
114112
}
115113

116114
internal static void OnBeforeUpdate(InputUpdateType type)
117115
{
118-
s_LastUpdateType = type;
116+
s_LatestUpdateType = type;
119117
switch (type)
120118
{
121119
case InputUpdateType.Dynamic:
122120
case InputUpdateType.Manual:
123121
case InputUpdateType.Fixed:
124122
s_PlayerUpdateStepCount.OnBeforeUpdate();
125123
s_UpdateStepCount = s_PlayerUpdateStepCount.value;
124+
#if UNITY_EDITOR
125+
s_LatestNonEditorUpdateType = type;
126+
#endif
126127
break;
127128
#if UNITY_EDITOR
128129
case InputUpdateType.Editor:
@@ -135,14 +136,17 @@ internal static void OnBeforeUpdate(InputUpdateType type)
135136

136137
internal static void OnUpdate(InputUpdateType type)
137138
{
138-
s_LastUpdateType = type;
139+
s_LatestUpdateType = type;
139140
switch (type)
140141
{
141142
case InputUpdateType.Dynamic:
142143
case InputUpdateType.Manual:
143144
case InputUpdateType.Fixed:
144145
s_PlayerUpdateStepCount.OnUpdate();
145146
s_UpdateStepCount = s_PlayerUpdateStepCount.value;
147+
#if UNITY_EDITOR
148+
s_LatestNonEditorUpdateType = type;
149+
#endif
146150
break;
147151
#if UNITY_EDITOR
148152
case InputUpdateType.Editor:
@@ -153,31 +157,38 @@ internal static void OnUpdate(InputUpdateType type)
153157
}
154158
}
155159

160+
#if UNITY_EDITOR
161+
internal static void RestoreStateAfterEditorUpdate()
162+
{
163+
s_LatestUpdateType = s_LatestNonEditorUpdateType;
164+
s_UpdateStepCount = s_PlayerUpdateStepCount.value;
165+
}
166+
167+
#endif
168+
156169
public static SerializedState Save()
157170
{
158171
return new SerializedState
159172
{
160-
lastUpdateType = s_LastUpdateType,
173+
lastUpdateType = s_LatestUpdateType,
161174
playerUpdateStepCount = s_PlayerUpdateStepCount,
162175
#if UNITY_EDITOR
163-
editorUpdateStepCount = s_EditorUpdateStepCount,
176+
lastNonEditorUpdateType = s_LatestNonEditorUpdateType,
177+
editorUpdateStepCount = s_EditorUpdateStepCount
164178
#endif
165-
lastUpdateRetainedEventBytes = s_LastUpdateRetainedEventBytes,
166-
lastUpdateRetainedEventCount = s_LastUpdateRetainedEventCount,
167179
};
168180
}
169181

170182
public static void Restore(SerializedState state)
171183
{
172-
s_LastUpdateType = state.lastUpdateType;
184+
s_LatestUpdateType = state.lastUpdateType;
173185
s_PlayerUpdateStepCount = state.playerUpdateStepCount;
174-
s_LastUpdateRetainedEventBytes = state.lastUpdateRetainedEventBytes;
175-
s_LastUpdateRetainedEventCount = state.lastUpdateRetainedEventCount;
176186
#if UNITY_EDITOR
187+
s_LatestNonEditorUpdateType = state.lastNonEditorUpdateType;
177188
s_EditorUpdateStepCount = state.editorUpdateStepCount;
178189
#endif
179190

180-
switch (s_LastUpdateType)
191+
switch (s_LatestUpdateType)
181192
{
182193
case InputUpdateType.Dynamic:
183194
case InputUpdateType.Manual:
@@ -216,5 +227,13 @@ public static bool IsPlayerUpdate(this InputUpdateType updateType)
216227
return false;
217228
return updateType != default;
218229
}
230+
231+
#if UNITY_EDITOR
232+
public static bool IsEditorUpdate(this InputUpdateType updateType)
233+
{
234+
return updateType == InputUpdateType.Editor;
235+
}
236+
237+
#endif
219238
}
220239
}

0 commit comments

Comments
 (0)