Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,10 @@
private double m_StartTimeAsPerRuntime;
private int m_AllEventsByTimeIndex = 0;
private List<InputEventPtr> m_AllEventsByTime;
#if UNITY_EDITOR
private bool m_ReplayBypassActive;
private Action m_ClearReplayBypassCallback;
#endif

internal ReplayController(InputEventTrace trace)
{
Expand All @@ -1088,12 +1092,65 @@
{
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.
private void BeginReplayBypass()
{
if (m_ClearReplayBypassCallback != null)
{
InputSystem.onAfterUpdate -= m_ClearReplayBypassCallback;
}

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

View check run for this annotation

Codecov GitHub.com / codecov/patch

Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs#L1110-L1112

Added lines #L1110 - L1112 were not covered by tests

if (!m_ReplayBypassActive)
{
m_ReplayBypassActive = true;
++InputSystem.s_Manager.m_ActiveReplayCount;
Comment thread
MorganHoarau marked this conversation as resolved.
Comment thread
MorganHoarau marked this conversation as resolved.
}
}

// 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;

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

View check run for this annotation

Codecov GitHub.com / codecov/patch

Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs#L1127

Added line #L1127 was not covered by tests

if (m_ClearReplayBypassCallback != null)
{
return;

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

View check run for this annotation

Codecov GitHub.com / codecov/patch

Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs#L1130-L1131

Added lines #L1130 - L1131 were not covered by tests
}

m_ClearReplayBypassCallback = EndReplayBypass;
InputSystem.onAfterUpdate += m_ClearReplayBypassCallback;
Comment thread
MorganHoarau marked this conversation as resolved.
}

private void EndReplayBypass()
{
if (m_ClearReplayBypassCallback != null)
{
InputSystem.onAfterUpdate -= m_ClearReplayBypassCallback;
m_ClearReplayBypassCallback = null;
}
if (m_ReplayBypassActive)
{
m_ReplayBypassActive = false;
if (InputSystem.s_Manager != null)
--InputSystem.s_Manager.m_ActiveReplayCount;
}
}

#endif
/// <summary>
/// Replay events recorded from <paramref name="recordedDevice"/> on device <paramref name="playbackDevice"/>.
/// </summary>
Expand Down Expand Up @@ -1249,6 +1306,9 @@
public ReplayController PlayAllFramesOneByOne()
{
finished = false;
#if UNITY_EDITOR
BeginReplayBypass();
#endif
InputSystem.onBeforeUpdate += OnBeginFrame;
return this;
}
Expand All @@ -1267,6 +1327,9 @@
public ReplayController PlayAllEvents()
{
finished = false;
#if UNITY_EDITOR
BeginReplayBypass();
#endif
try
{
while (MoveNext(true, out var eventPtr))
Expand Down Expand Up @@ -1311,6 +1374,9 @@

// Start playback.
finished = false;
#if UNITY_EDITOR
BeginReplayBypass();
#endif
m_StartTimeAsPerFirstEvent = -1;
m_AllEventsByTimeIndex = -1;
InputSystem.onBeforeUpdate += OnBeginFrame;
Expand Down Expand Up @@ -1381,6 +1447,11 @@
{
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();
}

Expand Down
20 changes: 16 additions & 4 deletions Packages/com.unity.inputsystem/InputSystem/InputManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -500,8 +500,18 @@ public bool runPlayerUpdatesInEditMode
set => m_RunPlayerUpdatesInEditMode = value;
}

/// <summary>
/// Number of active <see cref="InputEventTrace.ReplayController"/> 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.
/// </summary>
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;
Expand All @@ -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
Expand Down Expand Up @@ -3371,15 +3381,18 @@ 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
!isReplayActive &&
Comment thread
MorganHoarau marked this conversation as resolved.
#endif
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
Expand Down Expand Up @@ -3410,7 +3423,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 @@ -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
;
}

/// <summary>
Expand Down
Loading