InputSystem
InputSystem copied to clipboard
NEW: Improve performance when reading control values
Description
This commit introduces the concept of control value caching where the system marks controls that have been changed as stale and when reading control values, either through InputControl.Readvalue or InputAction.ReadValue, only stale controls read from the underlying storage and process the result before returning it. Unchanged controls simply return the cached value to the caller.
Changes made
The InputDeviceBuilder now creates a binary tree of ControlBitRangeNode elements to store the bit ranges that controls map to in the state memory of the device. The tree is stored in the device itself and when state is changed, either through a state event or through InputState.Change, it is traversed to figure out what controls have been changed. Changed controls are marked as stale and when reading the value of the control, only stale controls go to the device storage and pull out the value and run it through the control's processor stack.
Notes
The InputControl<TValue>.value property is ref readonly to improve performance when using some of the larger input control state structs, especially ones from XR like Bone and Eyes. This necessitated a change to those structs where all the properties have been made into fields so the readonly aspect can be enforced by the compiler at the call site, so now they're straight POCOs.
This PR also lays the ground work for some further optimizations, namely:
- removal of state monitors State monitors exist to watch sections of memory and fire off input action processing when those sections change. They are implemented in O(n) time as a straight linear walk through all monitors which can get expensive for fully loaded action maps. With this PR, we can now tell what controls have changed given a piece of new state memory, so state monitors might not be necessary.
- improved performance of any code that uses the InputControlEnumerator, such as rebinding, InputSystem.onEvent callbacks, and especially listening for unpaired device activitiy.
- makes it possible to correctly fix wasPressed/ReleasedThisFrame
Checklist
Before review:
- [x] Changelog entry added.
- Explains the change in
Changed
,Fixed
,Added
sections. - For API change contains an example snippet and/or migration example.
- FogBugz ticket attached, example
([case %number%](https://issuetracker.unity3d.com/issues/...))
. - FogBugz is marked as "Resolved" with next release version correctly set.
- Explains the change in
- [x] Tests added/changed, if applicable.
- Functional tests
Area_CanDoX
,Area_CanDoX_EvenIfYIsTheCase
,Area_WhenIDoX_AndYHappens_ThisIsTheResult
. - Performance tests.
- Integration tests.
- Functional tests
- [x] 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
.
-
Out of curiosity, what performance improvements have you observed with relation to these changes? Based off the description it sound like this is an enabler for further improvements.
Out of curiosity, what performance improvements have you observed with relation to these changes? Based off the description it sound like this is an enabler for further improvements.
Micro-benchmarks showed about a 2x performance improvement when reading the value of simple controls with no processors, and that gets better the more complex a control is i.e. more child controls, especially AxisControl, and better again the more processors a control has. One real world scenario that should give us a solid performance improvement from this PR alone though is having something like a four way composite attached to 16 different controls (something that's given us problems before, particularly on Switch). In the past, we'd read the value of each of those 16 controls at least twice per composite ReadValue call (once for the value and once for the magnitude check). Now we'll only read the value of the composite parts that have actually changed.
Beautiful work.
Beautiful work.
:bow: