Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIX: Add workaround for cases where button presses can be missed (ISXB-491) #1935

Merged
merged 17 commits into from
Jul 8, 2024

Conversation

graham-huws
Copy link
Collaborator

@graham-huws graham-huws commented May 24, 2024

Description

Fix for ISXB-491.
The current design of the input system package relies on the following method for checking if a button has been pressed on the current frame: Check the current value of the button, and compare to the final value of the previous frame.
The issue with this approach is that presses can be missed if they happened very quickly, or there was some form of lag making the update take longer than intended. Whether a button is pressed is only properly evaluated when asked, so if we don't check every input coming in, some can be missed.

Changes made

This PR seeks to solve this in a very simplistic way - check every value that affects ButtonControls and record their pressed state at the time the change comes in. Current belief is that a "clever" solution to this issue would require a fairly extensive rewrite of how the input system works.
Performance comparison data can be viewed on Sheet 2 here.

Checklist

Before review:

  • 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.
  • Tests added/changed, if applicable.
    • Functional tests Area_CanDoX, Area_CanDoX_EvenIfYIsTheCase, Area_WhenIDoX_AndYHappens_ThisIsTheResult.
    • Performance tests.
    • Integration 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.

@graham-huws graham-huws requested a review from ekcoh May 24, 2024 16:13
Copy link
Collaborator

@lyndon-unity lyndon-unity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to get a summary of the performance impact. The spreadsheet has lots of data but doesn't make it very clear what the overall impact is.

My main concern is that this fixes lost events but we are hiding it behind a flag. I'd like to know the impact of enabling the flag by default.

@lyndon-unity
Copy link
Collaborator

Added Paulius to help testing performance on Android

@Pauliusd01
Copy link
Collaborator

Pauliusd01 commented Jun 3, 2024

The original bug project crashes when built on my only Android testing phone (Xiaomi MI 8 Lite) so I used the QA mobile sensor testing project instead when idling and when interacting with the UI buttons. Not sure what to make of the results so I'm just gonna post the screenshot with analyzer data as well as profiler captures. Capture data

analyzer screenshots

(In the screenshot blue highlighted area is for simply idling the app and the right green area is me pressing UI buttons every 0.5~ sec)

@Pauliusd01 Pauliusd01 removed their request for review June 3, 2024 12:28
@lyndon-unity
Copy link
Collaborator

lyndon-unity commented Jun 3, 2024

These captures don't show any significant slow down so I think this is good to land (pending reacting to feedback above in PR)

@lyndon-unity
Copy link
Collaborator

Screenshot 2024-06-03 at 16 39 28 There is a slowdown on some frames for the PreUpdate.NewInputUpdate. 0.20 to 0.34 on one frame Only 2 frames show slow down more and 0.1 Screenshot 2024-06-03 at 16 41 14 InputProcess has some slow down but its minimal 0.03 to 0.04

How many taps were made in this sample ?

@Pauliusd01

This comment was marked as off-topic.

@Pauliusd01
Copy link
Collaborator

Here's one of me just tapping non stop for around 15 secs

image
ExtraCaptures2.zip

@lyndon-unity
Copy link
Collaborator

Here's one of me just tapping non stop for around 15 secs

Screenshot 2024-06-04 at 13 30 08 Sorting by time and looking at the slowest 100 frames (when there is active tap interation) there is a small slowdown in InputUpdate 0.01ms (0.06->0.07ms). But this seems reasonable for the reliability improvement of the edge detection from the fix.

Copy link
Collaborator

@lyndon-unity lyndon-unity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to see this decoupled from the feature flag as I see this as a critical fix that should always be enabled.

Packages/com.unity.inputsystem/CHANGELOG.md Outdated Show resolved Hide resolved
@graham-huws graham-huws force-pushed the ISXB-491-button-was-pressed-this-frame branch from e2b8bed to 6b544ee Compare June 6, 2024 14:24
@graham-huws graham-huws marked this pull request as ready for review June 6, 2024 14:28
@graham-huws
Copy link
Collaborator Author

Have updated to address above comments, plus some extra issues that cropped up. Have added new perf results to the second sheet of the gdoc.

@Pauliusd01 Pauliusd01 removed their request for review June 10, 2024 06:02
@@ -488,29 +487,47 @@ public void Controls_ValueCachingWorksAcrossEntireDeviceMemoryRange()
InputSystem.actions?.Disable();
#endif

var keyboard = InputSystem.AddDevice<Keyboard>();
// Value caching is supported for all controls, but with changes to button press detection, ButtonControls will
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks for updating this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, this makes me wonder, will this evaluation work for detecting "button behavior presses" for controls that are not buttons? E.g. transition of axis under the button behavior (magnitude based) within a sub-frame? Basically this is not unique to buttons in any way, it's all about avoiding aliasing in the sampling process. Not saying these changes not already carry a lot of value, but trying to understand what problems still exist. Hence I wonder if we should setup an additional test case similar to the one done for wasPresseed/wasReleased this frame w.r.t. e.g. axis, or maybe you already can say what we guarantee for that use case? (We could just say, use actions for something like that)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe what you mention will be detected just fine, eg. gamepad has a ButtonControl for "left stick up"?

Assets/Tests/InputSystem/CoreTests_State.cs Outdated Show resolved Hide resolved
@@ -3739,7 +3773,8 @@ internal unsafe bool UpdateState(InputDevice device, InputEvent* eventPtr, Input
deviceStateSize);
}

if (InputSettings.readValueCachingFeatureEnabled)
// Keyboard has enough ButtonControls that it's faster to find out which have actually changed here.
if (InputSettings.readValueCachingFeatureEnabled || m_Devices[deviceIndex] is Keyboard)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there is also a synergy with knowing the button count also here? It sounds like the benefit here is actually more related to the number of buttons on the device rather than it being a Keyboard? Hence maybe this should be a threshold based off number of buttons? Its fine as is but something like that would make it more future proof?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated - with some testing with the new changes, it seems that having about 45 buttons that have pressed/releasedThisFrame usage is where it becomes more efficient to take the cache value, so we now swap over at that point.

Copy link
Collaborator

@ekcoh ekcoh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am going to approve this one now from my perspective and not taking other reviewer comments into account. I left a few questions and minor comments though for consideration. Great to see this being adressed, thanks @graham-huws!

@ekcoh
Copy link
Collaborator

ekcoh commented Jun 18, 2024

@lyndon-unity Could you help summarise what additional updates are needed to transition this from "Request Changes"?

Copy link
Collaborator

@lyndon-unity lyndon-unity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've going to approve this, but ideally we'd follow up to adjust it to limit the performance impact by only applying the extra checks when there is code that needs it. (I..e I think only if wasPressedThisFrame or wasReleasedThisFrame are used by the user). Ideally we'd defer the work to the call site.

I would like to hear the XR team perspective of the performance impact as there is a risk this is unexceptable in their use case.
Ideally they would have the option to opt out of this performance cost if they don't need if for specific actions/bindings.

@graham-huws
Copy link
Collaborator Author

Now updated to a new version which only does processing for ButtonControls which have had wasPressedThisFrame/wasReleasedThisFrame called, saving a lot of performance when not many are being utilised.

Added V2 benchmarks to the spreadsheet, and new tests which try varying amounts of buttons to test to see the impact.

Copy link
Collaborator

@lyndon-unity lyndon-unity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the performance is improved for the cases with less calls to wasPressedThisFrame with this update then I'm comfortable with it landing as it fixes the key issue and limits the performance impact to the flows where the checks are needed.

(InputUpdate.s_LatestUpdateType.IsEditorUpdate() ? m_UpdateCountLastReleasedEditor : m_UpdateCountLastReleased);
#else
public bool wasPressedThisFrame => InputUpdate.s_UpdateStepCount == m_UpdateCountLastPressed;
return device.wasUpdatedThisFrame && currentlyPressed && !pressedLastFrame;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is much easier to read than before which is great.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if I'm understanding correctly, this old flow can still miss some rapid in frame press/release on the first check, since we haven't run though all the transitions.

Its good we have the more expensive flow opted in, so not always paying the cost if never used, but I'd like to see a comment above to state that this can miss a transition on the first ever call.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree on the comment. Then if you care for the first call you may at least call this once up front to get it triggered.

var pressedLastFrame = IsValueConsideredPressed(ReadValueFromPreviousFrame());
BeginTestingForFramePresses(currentlyPressed, pressedLastFrame);

return device.wasUpdatedThisFrame && !currentlyPressed && pressedLastFrame;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again this is easier to read which is a good change

Copy link
Collaborator

@ekcoh ekcoh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, but agree to Lyndons comment.

Did you check consistency with wasPerformedThisFrame? Since that one is only available on actions I think it could have value to have a test that sets up an action bound to button and then coevaluates wasPerformedThisFrame with outcome of wasPressedThisFrame and wasReleasedThisFrame.

(InputUpdate.s_LatestUpdateType.IsEditorUpdate() ? m_UpdateCountLastReleasedEditor : m_UpdateCountLastReleased);
#else
public bool wasPressedThisFrame => InputUpdate.s_UpdateStepCount == m_UpdateCountLastPressed;
return device.wasUpdatedThisFrame && currentlyPressed && !pressedLastFrame;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree on the comment. Then if you care for the first call you may at least call this once up front to get it triggered.

@graham-huws graham-huws force-pushed the ISXB-491-button-was-pressed-this-frame branch from 262a46f to c7e591d Compare July 2, 2024 14:18
@graham-huws graham-huws merged commit 511daef into develop Jul 8, 2024
77 of 79 checks passed
@graham-huws graham-huws deleted the ISXB-491-button-was-pressed-this-frame branch July 8, 2024 23:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants