From 0011dc4df865301bbb002a6de1a172447be44e08 Mon Sep 17 00:00:00 2001 From: Morgan Hoarau Date: Tue, 14 Apr 2026 15:27:47 +0100 Subject: [PATCH 01/11] Add InputManager isReplayActive flag Bypasses several game focused related logic gates to let replay input event reach playmode session. --- .../InputSystem/InputManager.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs index 128aca347c..b6d5549b97 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs @@ -500,8 +500,18 @@ public bool runPlayerUpdatesInEditMode set => m_RunPlayerUpdatesInEditMode = value; } + /// + /// Number of active instances currently replaying. + /// When greater than zero, focus-based gating is bypassed so that replayed events reach the game + /// regardless of Game View focus. This affects event routing (A), disabled-device discard (B), + /// and UI module processing (C). See ISXB-1319. + /// + internal int m_ActiveReplayCount; + + internal bool isReplayActive => m_ActiveReplayCount > 0; #endif // UNITY_EDITOR + private bool gameIsPlaying => #if UNITY_EDITOR (m_Runtime.isInPlayMode && !UnityEditor.EditorApplication.isPaused) || m_RunPlayerUpdatesInEditMode; @@ -512,7 +522,7 @@ public bool runPlayerUpdatesInEditMode private bool gameHasFocus => #if UNITY_EDITOR - m_RunPlayerUpdatesInEditMode || applicationHasFocus || gameShouldGetInputRegardlessOfFocus; + m_RunPlayerUpdatesInEditMode || applicationHasFocus || gameShouldGetInputRegardlessOfFocus || isReplayActive; #else applicationHasFocus || gameShouldGetInputRegardlessOfFocus; #endif @@ -3371,15 +3381,15 @@ 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. - if (device != null && !device.enabled && + // 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 && !isReplayActive && currentEventType != DeviceRemoveEvent.Type && currentEventType != DeviceConfigurationEvent.Type && (device.m_DeviceFlags & (InputDevice.DeviceFlags.DisabledInRuntime | InputDevice.DeviceFlags.DisabledWhileInBackground)) != 0) { #if UNITY_EDITOR - // If the device is disabled in the backend, getting events for them - // is something that indicates a problem in the backend so diagnose. if ((device.m_DeviceFlags & InputDevice.DeviceFlags.DisabledInRuntime) != 0) m_Diagnostics?.OnEventForDisabledDevice(currentEventReadPtr, device); #endif @@ -3410,7 +3420,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; } From 9343b0a9173c1ce119973dbbc1d3b983140360d3 Mon Sep 17 00:00:00 2001 From: Morgan Hoarau Date: Tue, 14 Apr 2026 15:28:49 +0100 Subject: [PATCH 02/11] Links ReplayController to InputManager Bit of logics to track replay controllers and bridge with InputManager. --- .../InputSystem/Events/InputEventTrace.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs b/Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs index c8b3a40413..16e4056481 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs @@ -1072,6 +1072,8 @@ public class ReplayController : IDisposable private double m_StartTimeAsPerRuntime; private int m_AllEventsByTimeIndex = 0; private List m_AllEventsByTime; + private bool m_ReplayBypassActive; + private Action m_ClearReplayBypassCallback; internal ReplayController(InputEventTrace trace) { @@ -1088,12 +1090,52 @@ public void Dispose() { InputSystem.onBeforeUpdate -= OnBeginFrame; finished = true; + EndReplayBypass(); foreach (var device in m_CreatedDevices) InputSystem.RemoveDevice(device); m_CreatedDevices = default; } + // Signals InputManager to treat events as if game view has focus, bypassing + // editor focus routing that would otherwise defer pointer/keyboard events to + // editor updates where they reach the editor UI instead of the game. + private void BeginReplayBypass() + { + if (!m_ReplayBypassActive) + { + m_ReplayBypassActive = true; + ++InputSystem.s_Manager.m_ActiveReplayCount; + } + } + + // Schedules the bypass to be cleared after the current OnUpdate finishes processing + // events (via onAfterUpdate). This ensures events already queued in the native buffer + // are still processed with the bypass active before it is removed. + private void ScheduleEndReplayBypass() + { + if (!m_ReplayBypassActive) + return; + + if (m_ClearReplayBypassCallback == null) + m_ClearReplayBypassCallback = EndReplayBypass; + InputSystem.onAfterUpdate += m_ClearReplayBypassCallback; + } + + private void EndReplayBypass() + { + if (m_ClearReplayBypassCallback != null) + { + InputSystem.onAfterUpdate -= m_ClearReplayBypassCallback; + m_ClearReplayBypassCallback = null; + } + if (m_ReplayBypassActive) + { + m_ReplayBypassActive = false; + --InputSystem.s_Manager.m_ActiveReplayCount; + } + } + /// /// Replay events recorded from on device . /// @@ -1249,6 +1291,7 @@ public ReplayController Rewind() public ReplayController PlayAllFramesOneByOne() { finished = false; + BeginReplayBypass(); InputSystem.onBeforeUpdate += OnBeginFrame; return this; } @@ -1267,6 +1310,7 @@ public ReplayController PlayAllFramesOneByOne() public ReplayController PlayAllEvents() { finished = false; + BeginReplayBypass(); try { while (MoveNext(true, out var eventPtr)) @@ -1311,6 +1355,7 @@ public ReplayController PlayAllEventsAccordingToTimestamps() // Start playback. finished = false; + BeginReplayBypass(); m_StartTimeAsPerFirstEvent = -1; m_AllEventsByTimeIndex = -1; InputSystem.onBeforeUpdate += OnBeginFrame; @@ -1381,6 +1426,9 @@ private void Finished() { finished = true; InputSystem.onBeforeUpdate -= OnBeginFrame; + // Schedule bypass removal for after the next OnUpdate, so any events already + // queued into the native buffer this frame are still processed with the bypass active. + ScheduleEndReplayBypass(); m_OnFinished?.Invoke(); } From 89f9dee4355b1ac6ba0cae0f07e2b7535997445e Mon Sep 17 00:00:00 2001 From: Morgan Hoarau Date: Tue, 14 Apr 2026 15:29:21 +0100 Subject: [PATCH 03/11] Bypass early out UIInputModule processing Uses InputManager.isReplayActive flag --- .../InputSystem/Plugins/UI/InputSystemUIInputModule.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs index adba24a25e..911c66c607 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs @@ -1001,7 +1001,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.isReplayActive +#endif + ; } /// From c52fea6f0a267d941ee5a9b6495a25207bb52ef1 Mon Sep 17 00:00:00 2001 From: Morgan Hoarau Date: Wed, 15 Apr 2026 15:05:26 +0100 Subject: [PATCH 04/11] Update Events.md --- Packages/com.unity.inputsystem/Documentation~/Events.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Packages/com.unity.inputsystem/Documentation~/Events.md b/Packages/com.unity.inputsystem/Documentation~/Events.md index f93ce87ba1..c9f29e6b80 100644 --- a/Packages/com.unity.inputsystem/Documentation~/Events.md +++ b/Packages/com.unity.inputsystem/Documentation~/Events.md @@ -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 UIToolkit `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 From 0c43abf175fae4df340e0f864261efbfed060fb6 Mon Sep 17 00:00:00 2001 From: Morgan Hoarau Date: Wed, 15 Apr 2026 15:05:52 +0100 Subject: [PATCH 05/11] Update CHANGELOG.md --- 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 2813c56348..e8fbbac999 100644 --- a/Packages/com.unity.inputsystem/CHANGELOG.md +++ b/Packages/com.unity.inputsystem/CHANGELOG.md @@ -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]. From a4e21c4177168fba7777d045cc764a7e46919573 Mon Sep 17 00:00:00 2001 From: Morgan Hoarau Date: Wed, 15 Apr 2026 15:07:18 +0100 Subject: [PATCH 06/11] format --- .../InputSystem/Plugins/UI/InputSystemUIInputModule.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs index 911c66c607..fbb9ff3fcb 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs @@ -1003,9 +1003,9 @@ private bool shouldIgnoreFocus // react. get => explictlyIgnoreFocus || InputRuntime.s_Instance.runInBackground #if UNITY_EDITOR - || InputSystem.s_Manager.isReplayActive + || InputSystem.s_Manager.isReplayActive #endif - ; + ; } /// From a18b084dd08178bdc46acb61bd8f1af8e222ce95 Mon Sep 17 00:00:00 2001 From: Morgan Hoarau Date: Wed, 15 Apr 2026 15:17:58 +0100 Subject: [PATCH 07/11] Adds missing UNITY_EDITOR define WinPlayer build made to ensure compilation --- .../InputSystem/Events/InputEventTrace.cs | 16 ++++++++++++++-- .../InputSystem/InputManager.cs | 5 ++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs b/Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs index 16e4056481..4ae6b0a5a4 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs @@ -1072,8 +1072,10 @@ public class ReplayController : IDisposable private double m_StartTimeAsPerRuntime; private int m_AllEventsByTimeIndex = 0; private List m_AllEventsByTime; +#if UNITY_EDITOR private bool m_ReplayBypassActive; private Action m_ClearReplayBypassCallback; +#endif internal ReplayController(InputEventTrace trace) { @@ -1090,13 +1092,15 @@ public void Dispose() { InputSystem.onBeforeUpdate -= OnBeginFrame; finished = true; +#if UNITY_EDITOR EndReplayBypass(); - +#endif foreach (var device in m_CreatedDevices) InputSystem.RemoveDevice(device); m_CreatedDevices = default; } +#if UNITY_EDITOR // Signals InputManager to treat events as if game view has focus, bypassing // editor focus routing that would otherwise defer pointer/keyboard events to // editor updates where they reach the editor UI instead of the game. @@ -1135,7 +1139,7 @@ private void EndReplayBypass() --InputSystem.s_Manager.m_ActiveReplayCount; } } - +#endif /// /// Replay events recorded from on device . /// @@ -1291,7 +1295,9 @@ public ReplayController Rewind() public ReplayController PlayAllFramesOneByOne() { finished = false; +#if UNITY_EDITOR BeginReplayBypass(); +#endif InputSystem.onBeforeUpdate += OnBeginFrame; return this; } @@ -1310,7 +1316,9 @@ public ReplayController PlayAllFramesOneByOne() public ReplayController PlayAllEvents() { finished = false; +#if UNITY_EDITOR BeginReplayBypass(); +#endif try { while (MoveNext(true, out var eventPtr)) @@ -1355,7 +1363,9 @@ public ReplayController PlayAllEventsAccordingToTimestamps() // Start playback. finished = false; +#if UNITY_EDITOR BeginReplayBypass(); +#endif m_StartTimeAsPerFirstEvent = -1; m_AllEventsByTimeIndex = -1; InputSystem.onBeforeUpdate += OnBeginFrame; @@ -1426,9 +1436,11 @@ private void Finished() { finished = true; InputSystem.onBeforeUpdate -= OnBeginFrame; +#if UNITY_EDITOR // Schedule bypass removal for after the next OnUpdate, so any events already // queued into the native buffer this frame are still processed with the bypass active. ScheduleEndReplayBypass(); +#endif m_OnFinished?.Invoke(); } diff --git a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs index b6d5549b97..94741bf73c 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs @@ -3383,7 +3383,10 @@ private unsafe void ProcessEventBuffer(InputUpdateType updateType, ref InputEven // 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 && !isReplayActive && + if (device != null && !device.enabled && +#if UNITY_EDITOR + !isReplayActive && +#endif currentEventType != DeviceRemoveEvent.Type && currentEventType != DeviceConfigurationEvent.Type && (device.m_DeviceFlags & (InputDevice.DeviceFlags.DisabledInRuntime | From caabc9449099e152f48d94c4bfda0c57685c300e Mon Sep 17 00:00:00 2001 From: Morgan Hoarau Date: Wed, 15 Apr 2026 17:11:05 +0100 Subject: [PATCH 08/11] Address PR feedback --- .../InputSystem/Events/InputEventTrace.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs b/Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs index 4ae6b0a5a4..7a6523a04d 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs @@ -1106,6 +1106,11 @@ public void Dispose() // editor updates where they reach the editor UI instead of the game. private void BeginReplayBypass() { + if (m_ClearReplayBypassCallback != null) + { + InputSystem.onAfterUpdate -= m_ClearReplayBypassCallback; + } + if (!m_ReplayBypassActive) { m_ReplayBypassActive = true; @@ -1121,8 +1126,12 @@ private void ScheduleEndReplayBypass() if (!m_ReplayBypassActive) return; - if (m_ClearReplayBypassCallback == null) - m_ClearReplayBypassCallback = EndReplayBypass; + if (m_ClearReplayBypassCallback != null) + { + return; + } + + m_ClearReplayBypassCallback = EndReplayBypass; InputSystem.onAfterUpdate += m_ClearReplayBypassCallback; } From 85438f00ebf1d7f1e508d3790b4d0c67a6bb3af9 Mon Sep 17 00:00:00 2001 From: Morgan Hoarau Date: Wed, 15 Apr 2026 17:29:05 +0100 Subject: [PATCH 09/11] format --- .../com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs b/Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs index 7a6523a04d..03c81baa50 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs @@ -1148,6 +1148,7 @@ private void EndReplayBypass() --InputSystem.s_Manager.m_ActiveReplayCount; } } + #endif /// /// Replay events recorded from on device . From e7b942039594f956f1db1d94fe353c249f1f36d9 Mon Sep 17 00:00:00 2001 From: Morgan Hoarau <122548697+MorganHoarau@users.noreply.github.com> Date: Thu, 16 Apr 2026 10:57:43 +0100 Subject: [PATCH 10/11] Apply suggestion on Doc --- Packages/com.unity.inputsystem/Documentation~/Events.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Packages/com.unity.inputsystem/Documentation~/Events.md b/Packages/com.unity.inputsystem/Documentation~/Events.md index c9f29e6b80..a5fb7ecbc5 100644 --- a/Packages/com.unity.inputsystem/Documentation~/Events.md +++ b/Packages/com.unity.inputsystem/Documentation~/Events.md @@ -224,7 +224,7 @@ 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 UIToolkit `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. +> **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. From 84673dbcbb020b82045fd038bc89141396c21762 Mon Sep 17 00:00:00 2001 From: Morgan Hoarau <122548697+MorganHoarau@users.noreply.github.com> Date: Thu, 16 Apr 2026 11:07:28 +0100 Subject: [PATCH 11/11] Adds null check in ReplayController disposal Co-authored-by: u-pr[bot] <205906871+u-pr[bot]@users.noreply.github.com> --- .../InputSystem/Events/InputEventTrace.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs b/Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs index 03c81baa50..108e811122 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs @@ -1145,7 +1145,8 @@ private void EndReplayBypass() if (m_ReplayBypassActive) { m_ReplayBypassActive = false; - --InputSystem.s_Manager.m_ActiveReplayCount; + if (InputSystem.s_Manager != null) + --InputSystem.s_Manager.m_ActiveReplayCount; } }