FIX: XR input replay preserves device usages
Description
Enhance InputEventTrace so device usages (e.g. LeftHand, RightHand) are captured, serialized, and restored when recording and replaying input. This prevents XR devices recreated from traces from losing their usages and failing to match XR control schemes or being overridden by live input.
Key changes:
- Device usages are serialized to JSON and stored in
DeviceInfowhen tracing input events, allowing accurate recreation of usages during replay. - The
WriteToandReadFrommethods now handle the newm_UsagesJsonfield so usages are included in the trace file and restored on load. - During device mapping in replay, usages from the original device are restored on the recreated device, and a
UsageChangednotification is triggered to ensure XR devices correctly participate in action resolution and control schemes. - Added
UsagesJsonWrapperto serialize/deserialize device usages as JSON, with helpers to convert between arrays andReadOnlyArray<InternedString>. - File format version updated so that older traces can still be deserialized correctly (they simply have no usages stored).
Testing status & QA
- Manually tested in the editor with:
- Meta Quest 3 controllers (OpenXR) using recorded traces.
- Mouse and keyboard input traces.
- Verified that:
- XR devices now replay correctly with their Left/Right usages restored.
- Saving and loading traces preserves XR device usages.
- All the XR recording still working even if Initialize XR on Startup is disabled.
- Older files still loading properly (without usages, as before).
No new automated tests have been added.
Overall Product Risks
Complexity: low Halo Effect: Low
Potential risk areas:
- Replay on new devices path go through this code changes, not only XR usage, although the change should be backwards compatible for non-XR devices.
Comments to reviewers
Main things to focus on:
- Verify XR input replay with controllers (Left/Right) and confirm usages are restored correctly and actions/control schemes behave as expected.
- Confirm that traces recorded prior to this change still load and replay without regressions (apart from not having usages stored).
[!NOTE] On some headsets (e.g. Meta Quest), the XRHMD tracking state may remain active even when the device appears idle. In those cases, replayed rotation can still compete with live HMD rotation unless rotation input is configured accordingly (e.g. PassThrough mode Input Action or headset powered off). This appears to be platform/Input system behaviour rather than an InputEventTrace issue.
Checklist
Before review:
- [ ] Changelog entry added.
- Explains the change in
Changed,Fixed,Addedsections. - For API change contains an example snippet and/or migration example.
- JIRA ticket linked, example (case %<ID>%). If it is a private issue, just add the case ID without a link.
- Jira port for the next release set as "Resolved".
- Explains the change in
- [ ] Tests added/changed, if applicable.
- Functional tests
Area_CanDoX,Area_CanDoX_EvenIfYIsTheCase,Area_WhenIDoX_AndYHappens_ThisIsTheResult. - Performance tests.
- Integration tests.
- Functional tests
- [ ] Docs for new/changed API's.
- Xmldoc cross references are set correctly.
- Added explanation how the API works.
- Usage code examples added.
- The manual is updated, if needed.
During merge:
- [ ] Commit message for squash-merge is prefixed with one of the list:
NEW: ___.FIX: ___.DOCS: ___.CHANGE: ___.RELEASE: 1.1.0-preview.3.
PR Reviewer Guide 🔍
Here are some key observations to aid the review process:
| ⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪ The PR involves changes to file serialization formats and core input replay logic, requiring careful verification of backward compatibility and side effects, but the code volume is small. |
| 🏅 Score: 88 The changes are well-structured, backward compatible, and address the specific issue with XR device usages; however, there is a potential risk involving device ID collisions during replay that should be addressed. |
| 🧪 No relevant tests |
| 🔒 No security concerns identified |
⚡ Recommended focus areas for reviewPossible Device ID Collision
ApplyDeviceMapping, the code uses deviceInfo.m_DeviceId (from the trace) to look up and reset a device in the current session via InputSystem.GetDeviceById. If the trace was recorded in a different session (e.g., loaded from a file), the trace device ID is arbitrary relative to the current session's devices. This could inadvertently reset an unrelated device that happens to share the same integer ID. Consider validating that the device found actually matches the trace device (e.g., by layout) or ensuring this logic only runs when appropriate. |
|
🤖 Helpful? Please react with 👍/👎 | Questions❓Please reach out in Slack #ask-u-pr-agent
PR Code Suggestions ✨
Explore these optional code suggestions:
| Category | Suggestion | Impact |
| Possible issue |
Fix invalid null assignment to struct
Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs [1517]
Suggestion importance[1-10]: 10__ Why: | High |
Prevent NullReferenceException on empty JSON
Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs [1630-1638]
Suggestion importance[1-10]: 7__ Why: | Medium | |
| ||
🤖 Helpful? Please react with 👍/👎 | Questions❓Please reach out in Slack #ask-u-pr-agent
Updated branch with the u-pr-agent sugestions, which spotted the device collision issue, so resseting should not be made on that code path
still tough this note being important to have in mind
[!NOTE] On some headsets (e.g. Meta Quest), the XRHMD tracking state may remain active even when the device appears idle. In those cases, replayed rotation can still compete with live HMD rotation unless rotation input is configured accordingly (e.g. PassThrough mode Input Action or headset powered off). This appears to be platform/Input system behaviour rather than an InputEventTrace issue.
Its only happening with the Rotation Input action from XR Tracked pose driver
the current cleaner workarround which is pretty straightforward is just making that input action "Passthrough".
That will make the new created XRHMD to "override" the real HMD rotation