Skip to content
Open
1 change: 1 addition & 0 deletions Packages/com.unity.inputsystem/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Fixed

- Fixed `InputRecorder` playback not starting when the Game View is not focused in the Editor [ISXB-1319](https://jira.unity3d.com/browse/ISXB-1319)
- Fixed a `NullReferenceException` thrown when removing all action maps [UUM-137116](https://jira.unity3d.com/browse/UUM-137116)
- Simplified default setting messaging by consolidating repetitive messages into a single HelpBox.
- Fixed a `NullPointerReferenceException` thrown in `InputManagerStateMonitors.FireStateChangeNotifications` logging by adding validation [UUM-136095].
Expand Down
3 changes: 3 additions & 0 deletions Packages/com.unity.inputsystem/Documentation~/Events.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ trace.Dispose();

Dispose event traces after use, so that they do not leak memory on the unmanaged (C++) memory heap.

> [!NOTE]
> **Keyboard text input is not replayed to UI text fields.** Keyboard state (key presses) is captured and replayed correctly and remains accessible via `Keyboard.current`. However, there is a known limitation with character delivery to UI Framework components (uGUI `InputField` or UI Toolkit `TextField`). These components receive text through a separate native pipeline that is not fed by event replay. As a result, text typed into UI text fields during recording will not appear during playback.

You can also write event traces out to files/streams, load them back in, and replay recorded streams.

```CSharp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1072,6 +1072,9 @@
private double m_StartTimeAsPerRuntime;
private int m_AllEventsByTimeIndex = 0;
private List<InputEventPtr> m_AllEventsByTime;
#if UNITY_EDITOR
private bool m_EditorEventPassthroughActive;
#endif

internal ReplayController(InputEventTrace trace)
{
Expand All @@ -1088,12 +1091,52 @@
{
InputSystem.onBeforeUpdate -= OnBeginFrame;
finished = true;

#if UNITY_EDITOR
StopEditorEventPassthrough();
#endif
foreach (var device in m_CreatedDevices)
InputSystem.RemoveDevice(device);
m_CreatedDevices = default;
}

#if UNITY_EDITOR
private void StartEditorEventPassthrough()
{
if (!m_EditorEventPassthroughActive)
{
m_EditorEventPassthroughActive = true;
InputManager.StartEditorEventPassthrough();
}
}

private void StopEditorEventPassthrough()
{
// Clean up any pending deferred stop.
InputSystem.onAfterUpdate -= DeferredStopEditorEventPassthrough;

if (m_EditorEventPassthroughActive)
{
m_EditorEventPassthroughActive = false;
InputManager.StopEditorEventPassthrough();
}
}

// Defers passthrough stop to after the current update so events already queued
// in the native buffer are still processed with passthrough active.
private void ScheduleStopEditorEventPassthrough()
{
if (!m_EditorEventPassthroughActive)
return;

Check warning on line 1129 in Packages/com.unity.inputsystem/InputSystem/Runtime/Events/InputEventTrace.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

Packages/com.unity.inputsystem/InputSystem/Runtime/Events/InputEventTrace.cs#L1129

Added line #L1129 was not covered by tests
InputSystem.onAfterUpdate += DeferredStopEditorEventPassthrough;
}

private void DeferredStopEditorEventPassthrough()
{
InputSystem.onAfterUpdate -= DeferredStopEditorEventPassthrough;
StopEditorEventPassthrough();
}

#endif
/// <summary>
/// Replay events recorded from <paramref name="recordedDevice"/> on device <paramref name="playbackDevice"/>.
/// </summary>
Expand Down Expand Up @@ -1249,6 +1292,9 @@
public ReplayController PlayAllFramesOneByOne()
{
finished = false;
#if UNITY_EDITOR
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, when this kind of behaviour is needed (with these editor checks all over the place), I wonder if we shouldn't add a proper optional callback for these replay events, e.g. ReplayBegun, there already seems to be an m_OnFinished that could be used to handle the call to editor code ScheduleEndReplayBypass()?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e.g. this could be e.g. (not sure about naming - conceptually) m_OnReplayStart?();

StartEditorEventPassthrough();
#endif
InputSystem.onBeforeUpdate += OnBeginFrame;
return this;
}
Expand All @@ -1267,6 +1313,9 @@
public ReplayController PlayAllEvents()
{
finished = false;
#if UNITY_EDITOR
StartEditorEventPassthrough();
#endif
try
{
while (MoveNext(true, out var eventPtr))
Expand Down Expand Up @@ -1311,6 +1360,9 @@

// Start playback.
finished = false;
#if UNITY_EDITOR
StartEditorEventPassthrough();
#endif
m_StartTimeAsPerFirstEvent = -1;
m_AllEventsByTimeIndex = -1;
InputSystem.onBeforeUpdate += OnBeginFrame;
Expand Down Expand Up @@ -1381,6 +1433,11 @@
{
finished = true;
InputSystem.onBeforeUpdate -= OnBeginFrame;
#if UNITY_EDITOR
// Defer passthrough stop so events already queued in the native buffer
// this frame are still processed with passthrough active.
ScheduleStopEditorEventPassthrough();
#endif
m_OnFinished?.Invoke();
}

Expand Down
45 changes: 43 additions & 2 deletions Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -492,8 +492,45 @@ public bool runPlayerUpdatesInEditMode
set => m_RunPlayerUpdatesInEditMode = value;
}

/// <summary>
/// Ref-counted flag that bypasses Game View focus gating for event processing.
/// When greater than zero, events are processed as if the Game View has focus,
/// regardless of actual focus state. This affects event routing, disabled-device
/// discard, and UI module processing.
/// </summary>
/// <remarks>
/// Use <see cref="StartEditorEventPassthrough"/> / <see cref="StopEditorEventPassthrough"/>
/// to manage this counter. Follows the same pattern as
/// <c>AssetDatabase.StartAssetEditing/StopAssetEditing</c>.
/// </remarks>
/// <seealso cref="StartEditorEventPassthrough"/>
/// <seealso cref="StopEditorEventPassthrough"/>
private int m_EditorEventPassthroughCount;

internal bool isEditorEventPassthroughActive => m_EditorEventPassthroughCount > 0;

/// <summary>
/// Signals that events should bypass Game View focus gating. Ref-counted:
/// each call must be balanced by a corresponding <see cref="StopEditorEventPassthrough"/>.
/// </summary>
internal static void StartEditorEventPassthrough()
{
++InputSystem.s_Manager.m_EditorEventPassthroughCount;
}

/// <summary>
/// Signals that the caller no longer needs events to bypass Game View focus gating.
/// Decrements the ref count started by <see cref="StartEditorEventPassthrough"/>.
/// </summary>
internal static void StopEditorEventPassthrough()
{
if (InputSystem.s_Manager != null && InputSystem.s_Manager.m_EditorEventPassthroughCount > 0)
--InputSystem.s_Manager.m_EditorEventPassthroughCount;
}

#endif // UNITY_EDITOR


private bool gameIsPlaying =>
#if UNITY_EDITOR
(m_Runtime.isInPlayMode && !m_Runtime.isEditorPaused) || m_RunPlayerUpdatesInEditMode;
Expand All @@ -504,7 +541,7 @@ public bool runPlayerUpdatesInEditMode

private bool gameHasFocus =>
#if UNITY_EDITOR
m_RunPlayerUpdatesInEditMode || applicationHasFocus || gameShouldGetInputRegardlessOfFocus;
m_RunPlayerUpdatesInEditMode || applicationHasFocus || gameShouldGetInputRegardlessOfFocus || isEditorEventPassthroughActive;
#else
applicationHasFocus || gameShouldGetInputRegardlessOfFocus;
#endif
Expand Down Expand Up @@ -3372,7 +3409,12 @@ private unsafe void ProcessEventBuffer(InputUpdateType updateType, ref InputEven

// If device is disabled, we let the event through only in certain cases.
// Removal and configuration change events should always be processed.
// During replay, allow events through for devices disabled due to background
// focus loss — the replay intentionally re-injects events for those devices.
if (device != null && !device.enabled &&
#if UNITY_EDITOR
!isEditorEventPassthroughActive &&
#endif
currentEventType != DeviceRemoveEvent.Type &&
currentEventType != DeviceConfigurationEvent.Type &&
(device.m_DeviceFlags & (InputDevice.DeviceFlags.DisabledInRuntime |
Expand Down Expand Up @@ -3411,7 +3453,6 @@ private unsafe void ProcessEventBuffer(InputUpdateType updateType, ref InputEven
#endif
if (!shouldProcess)
{
// Skip event if PreProcessEvent considers it to be irrelevant.
m_InputEventStream.Advance(false);
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -997,7 +997,11 @@ private bool shouldIgnoreFocus
// if running in the background is enabled, we already have rules in place what kind of input
// is allowed through and what isn't. And for the input that *IS* allowed through, the UI should
// react.
get => explictlyIgnoreFocus || InputRuntime.s_Instance.runInBackground;
get => explictlyIgnoreFocus || InputRuntime.s_Instance.runInBackground
#if UNITY_EDITOR
|| InputSystem.s_Manager.isEditorEventPassthroughActive
#endif
;
}

/// <summary>
Expand Down
Loading