Skip to content
Open
Show file tree
Hide file tree
Changes from 10 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 @@ public class ReplayController : IDisposable
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,64 @@ 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.
private void BeginReplayBypass()
{
if (m_ClearReplayBypassCallback != null)
{
InputSystem.onAfterUpdate -= m_ClearReplayBypassCallback;
}

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;

if (m_ClearReplayBypassCallback != null)
{
return;
}

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;
--InputSystem.s_Manager.m_ActiveReplayCount;
Comment thread
MorganHoarau marked this conversation as resolved.
Outdated
}
}

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

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

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