Skip to content

Commit 511daef

Browse files
authored
FIX: Add workaround for cases where button presses can be missed (ISXB-491) (#1935)
* FIX: Add workaround for cases where button presses can be missed (ISXB-491) * Update button press change to work without cached value feature flag. * Further improvements new tests, account for separate Editor input state. * Update changelog; expand button press testing * Missed format check * Address PR comments * Add flags so that additional processing only happens when users actually need it. * Fix up tests, most crucially setting relevant states when wasPressed/Released are first called. * Fix for 2019.4 being very, very old (HashSet capacity support). * Reduce test repetition to 250 as it was taking too long. Should still give plenty of info. * Only loop through buttons that need testing. Exclude tvOS from some tests where it times out for being too slow. * Fix up after merge + formatting. * Try boosting timeout value on tvOS to avoid test failures. * Undo change, can't be sure it's relevant due to other ongoing tvOS issues. * Exclude these other tests on tvOS as they seem to really trip up connection timeouts.
1 parent badaef8 commit 511daef

12 files changed

Lines changed: 533 additions & 30 deletions

File tree

.github/pull_request_template.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Before review:
2121
- [ ] Changelog entry added.
2222
- Explains the change in `Changed`, `Fixed`, `Added` sections.
2323
- For API change contains an example snippet and/or migration example.
24-
- JIRA ticket linked, example ([case %<ID>%](https://issuetracker.unity3d.com/product/unity/issues/guid/<ID>)). If it is a private issue, just add the case ID without a link.
24+
- JIRA ticket linked, example ([case %<ID>%](https://issuetracker.unity3d.com/product/unity/issues/guid/<ID>)). If it is a private issue, just add the case ID without a link.
2525
- Jira port for the next release set as "Resolved".
2626
- [ ] Tests added/changed, if applicable.
2727
- Functional tests `Area_CanDoX`, `Area_CanDoX_EvenIfYIsTheCase`, `Area_WhenIDoX_AndYHappens_ThisIsTheResult`.
@@ -44,4 +44,4 @@ During merge:
4444

4545
After merge:
4646

47-
- [ ] Create forward/backward port if needed. If you are blocked from creating a forward port now please add a task to ISX-1444.
47+
- [ ] Create forward/backward port if needed. If you are blocked from creating a forward port now please add a task to ISX-1444.

Assets/Tests/InputSystem/CorePerformanceTests.cs

Lines changed: 218 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using UnityEngine.InputSystem;
34
using UnityEngine.InputSystem.LowLevel;
45
using NUnit.Framework;
@@ -10,6 +11,7 @@
1011
using UnityEngine.InputSystem.Layouts;
1112
using UnityEngine.InputSystem.Users;
1213
using UnityEngine.InputSystem.Utilities;
14+
using UnityEngine.TestTools;
1315

1416
////TODO: add test for domain reload logic
1517

@@ -719,6 +721,94 @@ public void Performance_OptimizedControls_ReadAndUpdateGamepadNewValuesEveryFram
719721
.Run();
720722
}
721723

724+
// tvOS builders are way too slow for this and regularly time out, so skip there.
725+
[Test, Performance, UnityPlatform(exclude = new[] { RuntimePlatform.tvOS })]
726+
[Category("Performance")]
727+
[TestCase(OptimizationTestType.NoOptimization)]
728+
[TestCase(OptimizationTestType.ReadValueCaching)]
729+
// These tests shows a use case where ReadValueCaching optimization will perform better than without any
730+
// optimization.
731+
// It shows that there's a performance improvement when the control values being read are not changing every frame.
732+
//
733+
// NOTE: Performance is expected to be near-identical between the two optimisation settings, since Keyboard takes
734+
// the ReadValueCaching paths in UpdateState.
735+
public void Performance_OptimizedControls_ReadAndUpdateKeyboard1kTimes(OptimizationTestType testType)
736+
{
737+
SetInternalFeatureFlagsFromTestType(testType);
738+
739+
var keyboard = InputSystem.AddDevice<Keyboard>();
740+
741+
InputSystem.Update();
742+
743+
Measure.Method(() =>
744+
{
745+
InputSystem.QueueStateEvent(keyboard, new KeyboardState(Key.F));
746+
InputSystem.Update();
747+
748+
for (var i = 0; i < 1000; ++i)
749+
{
750+
InputSystem.Update();
751+
752+
if (i % 200 == 0)
753+
{
754+
// Make sure there's a new different value every 100 frames to mark the cached value as stale.
755+
InputSystem.QueueStateEvent(keyboard, new KeyboardState(Key.F));
756+
InputSystem.Update();
757+
}
758+
else if ((i + 100) % 200 == 0)
759+
{
760+
InputSystem.QueueStateEvent(keyboard, new KeyboardState());
761+
InputSystem.Update();
762+
}
763+
}
764+
})
765+
.MeasurementCount(100)
766+
.WarmupCount(10)
767+
.Run();
768+
}
769+
770+
// tvOS builders are way too slow for this and regularly time out, so skip there.
771+
[Test, Performance, UnityPlatform(exclude = new[] { RuntimePlatform.tvOS })]
772+
[Category("Performance")]
773+
[TestCase(OptimizationTestType.NoOptimization)]
774+
[TestCase(OptimizationTestType.ReadValueCaching)]
775+
// This shows a use case where ReadValueCaching optimization will perform worse when controls have stale cached
776+
// values every frame. Meaning, when control values change in every frame.
777+
//
778+
// NOTE: Performance is expected to be near-identical between the two optimisation settings, since Keyboard takes
779+
// the ReadValueCaching paths in UpdateState.
780+
public void Performance_OptimizedControls_ReadAndUpdateKeyboardNewValuesEveryFrame1kTimes(OptimizationTestType testType)
781+
{
782+
SetInternalFeatureFlagsFromTestType(testType);
783+
784+
var keyboard = InputSystem.AddDevice<Keyboard>();
785+
786+
InputSystem.Update();
787+
788+
Measure.Method(() =>
789+
{
790+
float val = keyboard.fKey.value;
791+
InputSystem.QueueStateEvent(keyboard, new KeyboardState(Key.F));
792+
InputSystem.Update();
793+
794+
for (var i = 0; i < 1000; ++i)
795+
{
796+
InputSystem.Update();
797+
val = keyboard.fKey.value;
798+
// Make sure there's a new different value every frames to mark the cached value as stale.
799+
InputSystem.QueueStateEvent(keyboard, new KeyboardState());
800+
801+
InputSystem.Update();
802+
val = keyboard.fKey.value;
803+
// Make sure there's a new different value every frames to mark the cached value as stale.
804+
InputSystem.QueueStateEvent(keyboard, new KeyboardState(Key.F));
805+
}
806+
})
807+
.MeasurementCount(100)
808+
.WarmupCount(10)
809+
.Run();
810+
}
811+
722812
[Test, Performance]
723813
[Category("Performance")]
724814
[TestCase(OptimizationTestType.NoOptimization)]
@@ -766,6 +856,132 @@ void CallUpdate()
766856
}
767857
}
768858

859+
// tvOS builders are way too slow for this and regularly time out, so skip there.
860+
[Test, Performance, UnityPlatform(exclude = new[] { RuntimePlatform.tvOS })]
861+
[Category("Performance")]
862+
[TestCase(OptimizationTestType.NoOptimization, 1)]
863+
[TestCase(OptimizationTestType.OptimizedControls, 1)]
864+
[TestCase(OptimizationTestType.ReadValueCaching, 1)]
865+
[TestCase(OptimizationTestType.OptimizedControlsAndReadValueCaching, 1)]
866+
867+
[TestCase(OptimizationTestType.NoOptimization, 33)]
868+
[TestCase(OptimizationTestType.OptimizedControls, 33)]
869+
[TestCase(OptimizationTestType.ReadValueCaching, 33)]
870+
[TestCase(OptimizationTestType.OptimizedControlsAndReadValueCaching, 33)]
871+
872+
[TestCase(OptimizationTestType.NoOptimization, 66)]
873+
[TestCase(OptimizationTestType.OptimizedControls, 66)]
874+
[TestCase(OptimizationTestType.ReadValueCaching, 66)]
875+
[TestCase(OptimizationTestType.OptimizedControlsAndReadValueCaching, 66)]
876+
877+
[TestCase(OptimizationTestType.NoOptimization, 100)]
878+
[TestCase(OptimizationTestType.OptimizedControls, 100)]
879+
[TestCase(OptimizationTestType.ReadValueCaching, 100)]
880+
[TestCase(OptimizationTestType.OptimizedControlsAndReadValueCaching, 100)]
881+
// Test the effect on performance that occurs when we check more and more buttons of the controller for their press/release within the frame.
882+
883+
public void Performance_OptimizedControls_Gamepad_250PressAndUpdate_WasPressedThisFrame_PercentButtonsTested(OptimizationTestType testType, float percentageOfButtonsTested)
884+
{
885+
SetInternalFeatureFlagsFromTestType(testType);
886+
887+
var gamepad = InputSystem.AddDevice<Gamepad>();
888+
InputSystem.Update();
889+
890+
var childrenThatAreButtons = new List<ButtonControl>();
891+
foreach (var child in gamepad.m_ChildrenForEachControl)
892+
{
893+
if (child.isButton)
894+
childrenThatAreButtons.Add((ButtonControl)child);
895+
}
896+
897+
var buttonsToTest = Math.Max(1, childrenThatAreButtons.Count * percentageOfButtonsTested / 100);
898+
for (int i = 0; i < buttonsToTest; ++i)
899+
{
900+
// Calling wasPressedThisFrame/wasReleasedThisFrame marks the button to care about the value,
901+
// which affects processing of future events to care about state changes so that this call is accurate.
902+
var press = childrenThatAreButtons[i].wasPressedThisFrame;
903+
}
904+
905+
Measure.Method(() =>
906+
{
907+
CallUpdate();
908+
})
909+
.MeasurementCount(100)
910+
.SampleGroup("Gamepad Only")
911+
.WarmupCount(10)
912+
.Run();
913+
914+
return;
915+
916+
void CallUpdate()
917+
{
918+
for (var i = 0; i < 250; ++i) PressAndRelease(gamepad.buttonSouth);
919+
}
920+
}
921+
922+
// tvOS builders are way too slow for this and regularly time out, so skip there.
923+
[Test, Performance, UnityPlatform(exclude = new[] { RuntimePlatform.tvOS })]
924+
[Category("Performance")]
925+
[TestCase(OptimizationTestType.NoOptimization, 1)]
926+
[TestCase(OptimizationTestType.OptimizedControls, 1)]
927+
[TestCase(OptimizationTestType.ReadValueCaching, 1)]
928+
[TestCase(OptimizationTestType.OptimizedControlsAndReadValueCaching, 1)]
929+
930+
[TestCase(OptimizationTestType.NoOptimization, 33)]
931+
[TestCase(OptimizationTestType.OptimizedControls, 33)]
932+
[TestCase(OptimizationTestType.ReadValueCaching, 33)]
933+
[TestCase(OptimizationTestType.OptimizedControlsAndReadValueCaching, 33)]
934+
935+
[TestCase(OptimizationTestType.NoOptimization, 66)]
936+
[TestCase(OptimizationTestType.OptimizedControls, 66)]
937+
[TestCase(OptimizationTestType.ReadValueCaching, 66)]
938+
[TestCase(OptimizationTestType.OptimizedControlsAndReadValueCaching, 66)]
939+
940+
[TestCase(OptimizationTestType.NoOptimization, 100)]
941+
[TestCase(OptimizationTestType.OptimizedControls, 100)]
942+
[TestCase(OptimizationTestType.ReadValueCaching, 100)]
943+
[TestCase(OptimizationTestType.OptimizedControlsAndReadValueCaching, 100)]
944+
// Test the effect on performance that occurs when we check more and more buttons of the controller for their press/release within the frame.
945+
946+
public void Performance_OptimizedControls_Keyboard_250PressAndUpdate_WasPressedThisFrame_PercentButtonsTested(OptimizationTestType testType, float percentageOfButtonsTested)
947+
{
948+
SetInternalFeatureFlagsFromTestType(testType);
949+
950+
var keyboard = InputSystem.AddDevice<Keyboard>();
951+
InputSystem.Update();
952+
953+
var childrenThatAreButtons = new List<ButtonControl>();
954+
foreach (var child in keyboard.m_ChildrenForEachControl)
955+
{
956+
if (child.isButton)
957+
childrenThatAreButtons.Add((ButtonControl)child);
958+
}
959+
960+
var buttonsToTest = Math.Max(1, childrenThatAreButtons.Count * percentageOfButtonsTested / 100);
961+
for (int i = 0; i < buttonsToTest; ++i)
962+
{
963+
// Calling wasPressedThisFrame/wasReleasedThisFrame marks the button to care about the value,
964+
// which affects processing of future events to care about state changes so that this call is accurate.
965+
var press = childrenThatAreButtons[i].wasPressedThisFrame;
966+
}
967+
968+
Measure.Method(() =>
969+
{
970+
CallUpdate();
971+
})
972+
.MeasurementCount(100)
973+
.SampleGroup("Keyboard Only")
974+
.WarmupCount(10)
975+
.Run();
976+
977+
return;
978+
979+
void CallUpdate()
980+
{
981+
for (var i = 0; i < 250; ++i) PressAndRelease(keyboard.fKey);
982+
}
983+
}
984+
769985
[Test, Performance]
770986
[Category("Performance")]
771987
[TestCase(OptimizationTestType.NoOptimization)]
@@ -782,7 +998,7 @@ public void Performance_OptimizedControls_EvaluateStaleControlReadsWhenGamepadSt
782998

783999
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
7841000
// Disable the project wide actions actions to avoid performance impact.
785-
InputSystem.actions.Disable();
1001+
InputSystem.actions?.Disable();
7861002
#endif
7871003

7881004
Measure.Method(() =>
@@ -824,7 +1040,7 @@ public void Performance_OptimizedControls_EvaluateStaleControlReadsWhenGamepadSt
8241040

8251041
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
8261042
// Re-enable the project wide actions actions.
827-
InputSystem.actions.Enable();
1043+
InputSystem.actions?.Enable();
8281044
#endif
8291045
return;
8301046

Assets/Tests/InputSystem/CoreTests_Controls.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
using UnityEngine.InputSystem.Processors;
1616
using UnityEngine.InputSystem.Utilities;
1717
using UnityEngine.Profiling;
18-
using UnityEngine.Scripting;
1918
using UnityEngine.TestTools.Constraints;
2019
using Is = UnityEngine.TestTools.Constraints.Is;
2120

@@ -476,6 +475,9 @@ public unsafe void Controls_ValueIsReadFromStateMemoryOnlyWhenControlHasBeenMark
476475

477476
[Test]
478477
[Category("Controls")]
478+
// NOTE! Value caching will never be true for ButtonControls where users call wasPressedThisFrame/wasReleasedThisFrame
479+
// following the first frame where either of those are called.
480+
// This doesn't apply to this test, but just in case it gets edited/duplicated in future...
479481
public void Controls_ValueCachingWorksAcrossEntireDeviceMemoryRange()
480482
{
481483
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS

Assets/Tests/InputSystem/CoreTests_State.cs

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -461,12 +461,15 @@ public void State_CanDetectWhetherButtonStateHasChangedThisFrame()
461461
Assert.That(gamepad.buttonEast.wasReleasedThisFrame, Is.True);
462462
}
463463

464-
// The way we keep state does not allow observing the state change on the final
465-
// state of the button. However, actions will still see the change.
466464
[Test]
467465
[Category("State")]
468-
public void State_PressingAndReleasingButtonInSameFrame_DoesNotShowStateChange()
466+
[TestCase(true)]
467+
[TestCase(false)]
468+
public void State_PressingAndReleasingButtonInSameFrame_ShowsStateChange(bool usesReadValueCaching)
469469
{
470+
var originalSetting = InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kUseReadValueCaching);
471+
InputSystem.settings.SetInternalFeatureFlag(InputFeatureNames.kUseReadValueCaching, usesReadValueCaching);
472+
470473
var gamepad = InputSystem.AddDevice<Gamepad>();
471474

472475
var firstState = new GamepadState {buttons = 1 << (int)GamepadButton.B};
@@ -477,9 +480,45 @@ public void State_PressingAndReleasingButtonInSameFrame_DoesNotShowStateChange()
477480

478481
InputSystem.Update();
479482

483+
// We don't listen for inter-frame press/releases until we see them being requested, so the first time we try
484+
// to detect it for a given device+ButtonControl, we'll miss the event.
480485
Assert.That(gamepad.buttonEast.isPressed, Is.False);
481486
Assert.That(gamepad.buttonEast.wasPressedThisFrame, Is.False);
482487
Assert.That(gamepad.buttonEast.wasReleasedThisFrame, Is.False);
488+
489+
InputSystem.QueueStateEvent(gamepad, firstState);
490+
InputSystem.QueueStateEvent(gamepad, secondState);
491+
492+
InputSystem.Update();
493+
494+
Assert.That(gamepad.buttonEast.isPressed, Is.False);
495+
Assert.That(gamepad.buttonEast.wasPressedThisFrame, Is.True);
496+
Assert.That(gamepad.buttonEast.wasReleasedThisFrame, Is.True);
497+
498+
InputSystem.QueueStateEvent(gamepad, firstState);
499+
InputSystem.QueueStateEvent(gamepad, secondState);
500+
InputSystem.QueueStateEvent(gamepad, firstState);
501+
502+
InputSystem.Update();
503+
504+
Assert.That(gamepad.buttonEast.isPressed, Is.True);
505+
Assert.That(gamepad.buttonEast.wasPressedThisFrame, Is.True);
506+
Assert.That(gamepad.buttonEast.wasReleasedThisFrame, Is.True);
507+
508+
InputSystem.QueueStateEvent(gamepad, firstState);
509+
InputSystem.QueueStateEvent(gamepad, secondState);
510+
InputSystem.QueueStateEvent(gamepad, firstState);
511+
InputSystem.QueueStateEvent(gamepad, secondState);
512+
InputSystem.QueueStateEvent(gamepad, firstState);
513+
InputSystem.QueueStateEvent(gamepad, secondState);
514+
515+
InputSystem.Update();
516+
517+
Assert.That(gamepad.buttonEast.isPressed, Is.False);
518+
Assert.That(gamepad.buttonEast.wasPressedThisFrame, Is.True);
519+
Assert.That(gamepad.buttonEast.wasReleasedThisFrame, Is.True);
520+
521+
InputSystem.settings.SetInternalFeatureFlag(InputFeatureNames.kUseReadValueCaching, originalSetting);
483522
}
484523

485524
[Test]

Packages/com.unity.inputsystem/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ however, it has to be formatted properly to pass verification tests.
1717

1818
### Fixed
1919
- Avoid potential crashes from `NullReferenceException` in `FireStateChangeNotifications`.
20+
- Fixed cases where `wasPressedThisFrame` would not return true if a press and release happened within the same frame before being queried (and vice versa for `wasReleasedThisFrame`).
2021
- Fixed an issue where a composite binding would not be consecutively triggered after ResetDevice() has been called from the associated action handler [ISXB-746](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-746).
2122
- Fixed resource designation for "d_InputControl" icon to address CI failure.
2223
- Fixed an issue where a composite binding would not be consecutively triggered after disabling actions while there are action modifiers in progress [ISXB-505](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-505).

0 commit comments

Comments
 (0)