@@ -59,6 +59,25 @@ internal static InputAction SetupTestAction(this InputActionMap map, string[] bi
5959
6060internal partial class CoreTests
6161{
62+ /// <summary>
63+ /// Overlap resolution uses <see cref="InputAction.Priority"/> and per-control grouping written from actions.
64+ /// </summary>
65+ private static void EnableActionPriorityShortcutResolution ( )
66+ {
67+ InputSystem . settings . shortcutKeysUseActionPriority = true ;
68+ InputSystem . settings . shortcutKeysConsumeInput = false ;
69+ }
70+
71+ /// <summary>
72+ /// Overlap resolution uses composite binding complexity; <see cref="InputAction.Priority"/> is not applied at runtime.
73+ /// Requires shortcut consumption on so control grouping merges slots on the same physical control.
74+ /// </summary>
75+ private static void EnableComplexityShortcutResolution ( )
76+ {
77+ InputSystem . settings . shortcutKeysConsumeInput = true ;
78+ InputSystem . settings . shortcutKeysUseActionPriority = false ;
79+ }
80+
6281 private static readonly List < ( string [ ] , string [ ] ) > k_TwoInputActionTestCases = new ( )
6382 {
6483 ( new [ ] { "ctrl" , "x" } , new [ ] { "x" } ) ,
@@ -111,6 +130,7 @@ private void ReleaseBindingsForActions(Keyboard keyboard, InputAction action1, I
111130 [ TestCaseSource ( nameof ( k_TwoInputActionTestCases ) ) ]
112131 public void Actions_Priority_OnlyOneActionIsFired_WhenOnePriorityIsHigherThanOther ( ( string [ ] a1 , string [ ] a2 ) actions )
113132 {
133+ EnableActionPriorityShortcutResolution ( ) ;
114134 var keyboard = InputSystem . AddDevice < Keyboard > ( ) ;
115135
116136 InputActionMap map = new InputActionMap ( "map" ) ;
@@ -145,6 +165,7 @@ public void Actions_Priority_OnlyOneActionIsFired_WhenOnePriorityIsHigherThanOth
145165 [ TestCaseSource ( nameof ( k_TwoInputActionTestCases ) ) ]
146166 public void Actions_Priority_OnlyOneActionIsFired_WhenOnePriorityIsHigherThanOtherInversePriorityOrder ( ( string [ ] a1 , string [ ] a2 ) actions )
147167 {
168+ EnableActionPriorityShortcutResolution ( ) ;
148169 var keyboard = InputSystem . AddDevice < Keyboard > ( ) ;
149170
150171 InputActionMap map = new InputActionMap ( "map" ) ;
@@ -179,6 +200,7 @@ public void Actions_Priority_OnlyOneActionIsFired_WhenOnePriorityIsHigherThanOth
179200 [ TestCaseSource ( nameof ( k_TwoInputActionTestCases ) ) ] // TODO: Darren, Should both actions be performed this frame here??
180201 public void Actions_Priority_BothActionsArePerformed_DueToKeyPressOrderForShortcut ( ( string [ ] larger , string [ ] smaller ) actions )
181202 {
203+ EnableActionPriorityShortcutResolution ( ) ;
182204 var keyboard = InputSystem . AddDevice < Keyboard > ( ) ;
183205
184206 InputActionMap map = new InputActionMap ( "map" ) ;
@@ -219,6 +241,7 @@ public void Actions_Priority_BothActionsArePerformed_DueToKeyPressOrderForShortc
219241 [ TestCaseSource ( nameof ( k_TwoInputActionTestCases ) ) ]
220242 public void Actions_Priority_BothActionFires_WhenPriorityIsEqual ( ( string [ ] a1 , string [ ] a2 ) actions )
221243 {
244+ EnableActionPriorityShortcutResolution ( ) ;
222245 var keyboard = InputSystem . AddDevice < Keyboard > ( ) ;
223246
224247 InputActionMap map = new InputActionMap ( "map" ) ;
@@ -242,6 +265,7 @@ public void Actions_Priority_BothActionFires_WhenPriorityIsEqual((string[] a1, s
242265 [ TestCaseSource ( nameof ( k_TwoInputActionTestCases ) ) ]
243266 public void Actions_Priority_BothActionsFire_WhenPriorityIsZero ( ( string [ ] a1 , string [ ] a2 ) actions )
244267 {
268+ EnableActionPriorityShortcutResolution ( ) ;
245269 var keyboard = InputSystem . AddDevice < Keyboard > ( ) ;
246270
247271 InputActionMap map = new InputActionMap ( "map" ) ;
@@ -279,6 +303,7 @@ public void Actions_Priority_BothActionsFire_WhenPriorityIsZero((string[] a1, st
279303 [ TestCaseSource ( nameof ( k_TwoInputActionNoConflictingBindingTestCases ) ) ]
280304 public void Actions_Priority_BothActionsWithDifferentPriorityFire_WhenThereIsNoConflictingBinding ( ( string [ ] a1 , string [ ] a2 ) actions )
281305 {
306+ EnableActionPriorityShortcutResolution ( ) ;
282307 var keyboard = InputSystem . AddDevice < Keyboard > ( ) ;
283308
284309 InputActionMap map = new InputActionMap ( "map" ) ;
@@ -309,6 +334,7 @@ public void Actions_Priority_BothActionsWithDifferentPriorityFire_WhenThereIsNoC
309334 [ TestCaseSource ( nameof ( k_TwoInputActionNoConflictingBindingTestCases ) ) ]
310335 public void Actions_Priority_BothActionsWithDifferentPriorityFire_WhenThereIsNoConflictingBindingInverseOrder ( ( string [ ] a1 , string [ ] a2 ) actions )
311336 {
337+ EnableActionPriorityShortcutResolution ( ) ;
312338 var keyboard = InputSystem . AddDevice < Keyboard > ( ) ;
313339
314340 InputActionMap map = new InputActionMap ( "map" ) ;
@@ -339,6 +365,7 @@ public void Actions_Priority_BothActionsWithDifferentPriorityFire_WhenThereIsNoC
339365 [ TestCaseSource ( nameof ( k_TwoInputActionNoConflictingBindingTestCases ) ) ]
340366 public void Actions_Priority_BothActionsWithEqualPriorityFire_WhenThereIsNoConflictingBinding ( ( string [ ] a1 , string [ ] a2 ) actions )
341367 {
368+ EnableActionPriorityShortcutResolution ( ) ;
342369 var keyboard = InputSystem . AddDevice < Keyboard > ( ) ;
343370
344371 InputActionMap map = new InputActionMap ( "map" ) ;
@@ -420,6 +447,7 @@ public void Actions_Priority_InputActionStateMonitorIndex_ImplicitConversionToLo
420447 [ Category ( "Actions Priority" ) ]
421448 public unsafe void Actions_Priority_ControlGrouping_SamePhysicalControlSharesGroupId ( )
422449 {
450+ EnableActionPriorityShortcutResolution ( ) ;
423451 var keyboard = InputSystem . AddDevice < Keyboard > ( ) ;
424452 var map = new InputActionMap ( "priority_group_test" ) ;
425453 map . AddAction ( "a" , binding : "<Keyboard>/z" ) ;
@@ -452,6 +480,7 @@ public unsafe void Actions_Priority_ControlGrouping_SamePhysicalControlSharesGro
452480 [ Category ( "Actions Priority" ) ]
453481 public unsafe void Actions_Priority_ControlGrouping_WritesPerControlSlotPriorityFromAction ( )
454482 {
483+ EnableActionPriorityShortcutResolution ( ) ;
455484 var keyboard = InputSystem . AddDevice < Keyboard > ( ) ;
456485 var map = new InputActionMap ( "priority_per_slot_test" ) ;
457486 var actionLow = map . AddAction ( "low" , binding : "<Keyboard>/x" ) ;
@@ -494,6 +523,7 @@ public unsafe void Actions_Priority_ControlGrouping_WritesPerControlSlotPriority
494523 [ Category ( "Actions Priority" ) ]
495524 public IEnumerator Actions_Priority_BothActionsArePerformed_WhenAHoldAndBasicActionHaveDifferentTiming ( )
496525 {
526+ EnableActionPriorityShortcutResolution ( ) ;
497527 var keyboard = InputSystem . AddDevice < Keyboard > ( ) ;
498528 using var map = new InputActionMap ( "HoldChord" ) ;
499529
@@ -543,6 +573,7 @@ public IEnumerator Actions_Priority_BothActionsArePerformed_WhenAHoldAndBasicAct
543573 [ Category ( "Actions Priority" ) ]
544574 public IEnumerator Actions_Priority_OnlyOneHoldActionIsPerformed_WhenOnePriorityIsHigher ( )
545575 {
576+ EnableActionPriorityShortcutResolution ( ) ;
546577 var keyboard = InputSystem . AddDevice < Keyboard > ( ) ;
547578 using var map = new InputActionMap ( "HoldChord" ) ;
548579
@@ -583,4 +614,213 @@ public IEnumerator Actions_Priority_OnlyOneHoldActionIsPerformed_WhenOnePriority
583614 Release ( keyboard . leftShiftKey ) ;
584615 map . Disable ( ) ;
585616 }
617+
618+ [ Test ]
619+ [ Category ( "Actions Priority" ) ]
620+ public unsafe void Actions_Complexity_ControlGrouping_SamePhysicalControlSharesGroupId_WhenShortcutConsumptionEnabled ( )
621+ {
622+ EnableComplexityShortcutResolution ( ) ;
623+
624+ InputSystem . AddDevice < Keyboard > ( ) ;
625+ var map = new InputActionMap ( "complexity_group_test" ) ;
626+ map . AddAction ( "a" , binding : "<Keyboard>/z" ) ;
627+ map . AddAction ( "b" , binding : "<Keyboard>/z" ) ;
628+ map . Enable ( ) ;
629+
630+ var state = map . m_State ;
631+ Assert . That ( state , Is . Not . Null ) ;
632+ Assert . That ( state . memory . controlGroupingInitialized , Is . True ) ;
633+
634+ for ( var i = 0 ; i < state . totalControlCount ; ++ i )
635+ {
636+ for ( var j = i + 1 ; j < state . totalControlCount ; ++ j )
637+ {
638+ if ( state . controls [ i ] != state . controls [ j ] )
639+ continue ;
640+
641+ var gi = InputActionState . ControlGroupingTable . GroupElementIndex ( i ) ;
642+ var gj = InputActionState . ControlGroupingTable . GroupElementIndex ( j ) ;
643+ Assert . That ( state . memory . controlGroupingAndPriority [ gi ] , Is . EqualTo ( state . memory . controlGroupingAndPriority [ gj ] ) ) ;
644+ Assert . That ( state . memory . controlGroupingAndPriority [ gi ] , Is . Not . EqualTo ( 0 ) ) ;
645+ return ;
646+ }
647+ }
648+
649+ Assert . Fail ( "Expected two control slots bound to the same physical control." ) ;
650+ }
651+
652+ [ Test ]
653+ [ Category ( "Actions Priority" ) ]
654+ public unsafe void Actions_Complexity_ControlGrouping_WritesPerControlSlotComplexity_NotActionPriority ( )
655+ {
656+ EnableComplexityShortcutResolution ( ) ;
657+
658+ var keyboard = InputSystem . AddDevice < Keyboard > ( ) ;
659+ var map = new InputActionMap ( "complexity_per_slot_test" ) ;
660+ var actionLow = map . AddAction ( "low" , binding : "<Keyboard>/x" ) ;
661+ var actionHigh = map . AddAction ( "high" , binding : "<Keyboard>/x" ) ;
662+ actionLow . Priority = 4 ;
663+ actionHigh . Priority = 11 ;
664+ map . Enable ( ) ;
665+
666+ var state = map . m_State ;
667+ Assert . That ( state , Is . Not . Null ) ;
668+
669+ var lowIndex = - 1 ;
670+ var highIndex = - 1 ;
671+ for ( var i = 0 ; i < state . totalControlCount ; ++ i )
672+ {
673+ if ( state . controls [ i ] != keyboard . xKey )
674+ continue ;
675+ var bindingIndex = state . controlIndexToBindingIndex [ i ] ;
676+ var actionIndex = state . bindingStates [ bindingIndex ] . actionIndex ;
677+ if ( actionIndex == actionLow . m_ActionIndexInState )
678+ lowIndex = i ;
679+ else if ( actionIndex == actionHigh . m_ActionIndexInState )
680+ highIndex = i ;
681+ }
682+
683+ Assert . That ( lowIndex , Is . GreaterThanOrEqualTo ( 0 ) ) ;
684+ Assert . That ( highIndex , Is . GreaterThanOrEqualTo ( 0 ) ) ;
685+
686+ var pLow = InputActionState . ControlGroupingTable . PriorityElementIndex ( lowIndex ) ;
687+ var pHigh = InputActionState . ControlGroupingTable . PriorityElementIndex ( highIndex ) ;
688+ // Secondary column stores composite complexity; two simple bindings on the same key both have depth 1.
689+ Assert . That ( state . memory . controlGroupingAndPriority [ pLow ] , Is . EqualTo ( 1 ) ) ;
690+ Assert . That ( state . memory . controlGroupingAndPriority [ pHigh ] , Is . EqualTo ( 1 ) ) ;
691+ }
692+
693+ [ Test ]
694+ [ Category ( "Actions Priority" ) ]
695+ public unsafe void Actions_Complexity_ControlGrouping_WritesHigherComplexityOnSharedControlVersusSimpleBinding ( )
696+ {
697+ EnableComplexityShortcutResolution ( ) ;
698+
699+ var keyboard = InputSystem . AddDevice < Keyboard > ( ) ;
700+ var map = new InputActionMap ( "complexity_composite_vs_simple" ) ;
701+ var composite = map . AddAction ( "chord" , binding : null ) ;
702+ composite . AddCompositeBinding ( "OneModifier" )
703+ . With ( "Modifier" , "<Keyboard>/ctrl" )
704+ . With ( "Binding" , "<Keyboard>/x" ) ;
705+ var simple = map . AddAction ( "plain" , binding : "<Keyboard>/x" ) ;
706+ composite . Priority = 0 ;
707+ simple . Priority = 99 ;
708+ map . Enable ( ) ;
709+
710+ var state = map . m_State ;
711+ Assert . That ( state , Is . Not . Null ) ;
712+
713+ var compositeXIndex = - 1 ;
714+ var simpleXIndex = - 1 ;
715+ for ( var i = 0 ; i < state . totalControlCount ; ++ i )
716+ {
717+ if ( state . controls [ i ] != keyboard . xKey )
718+ continue ;
719+ var bindingIndex = state . controlIndexToBindingIndex [ i ] ;
720+ var actionIndex = state . bindingStates [ bindingIndex ] . actionIndex ;
721+ if ( actionIndex == composite . m_ActionIndexInState )
722+ compositeXIndex = i ;
723+ else if ( actionIndex == simple . m_ActionIndexInState )
724+ simpleXIndex = i ;
725+ }
726+
727+ Assert . That ( compositeXIndex , Is . GreaterThanOrEqualTo ( 0 ) ) ;
728+ Assert . That ( simpleXIndex , Is . GreaterThanOrEqualTo ( 0 ) ) ;
729+
730+ var pComposite = InputActionState . ControlGroupingTable . PriorityElementIndex ( compositeXIndex ) ;
731+ var pSimple = InputActionState . ControlGroupingTable . PriorityElementIndex ( simpleXIndex ) ;
732+ Assert . That ( state . memory . controlGroupingAndPriority [ pSimple ] , Is . EqualTo ( 1 ) ) ;
733+ Assert . That (
734+ state . memory . controlGroupingAndPriority [ pComposite ] ,
735+ Is . GreaterThan ( state . memory . controlGroupingAndPriority [ pSimple ] ) ,
736+ "Composite binding chain depth should exceed a simple binding on the same physical control." ) ;
737+ }
738+
739+ [ Test ]
740+ [ Category ( "Actions Priority" ) ]
741+ [ TestCaseSource ( nameof ( k_TwoInputActionTestCases ) ) ]
742+ public void Actions_Complexity_CompositeWinsOverlappingSimple_IgnoresActionPriority ( ( string [ ] a1 , string [ ] a2 ) actions )
743+ {
744+ EnableComplexityShortcutResolution ( ) ;
745+
746+ var keyboard = InputSystem . AddDevice < Keyboard > ( ) ;
747+ var map = new InputActionMap ( "map" ) ;
748+
749+ var actionComposite = map . SetupTestAction ( actions . a1 ) ;
750+ var actionSimple = map . SetupTestAction ( actions . a2 ) ;
751+
752+ // Deliberately favor the simple binding in the Priority field; complexity resolution must still prefer the composite.
753+ actionComposite . Priority = 0 ;
754+ actionSimple . Priority = 100 ;
755+
756+ map . Enable ( ) ;
757+
758+ Assert . That ( actionComposite . WasPerformedThisFrame ( ) , Is . False ) ;
759+ Assert . That ( actionSimple . WasPerformedThisFrame ( ) , Is . False ) ;
760+
761+ PressBindingsForInputActions ( keyboard , actionComposite , actionSimple ) ;
762+
763+ Assert . That ( actionComposite . WasPerformedThisFrame ( ) , Is . True ) ;
764+ Assert . That ( actionSimple . WasPerformedThisFrame ( ) , Is . False ) ;
765+
766+ ReleaseBindingsForActions ( keyboard , actionComposite , actionSimple ) ;
767+
768+ InputSystem . Update ( ) ;
769+
770+ Assert . That ( actionComposite . WasPerformedThisFrame ( ) , Is . False ) ;
771+ Assert . That ( actionSimple . WasPerformedThisFrame ( ) , Is . False ) ;
772+ }
773+
774+ [ Test ]
775+ [ Category ( "Actions Priority" ) ]
776+ [ TestCaseSource ( nameof ( k_TwoInputActionTestCases ) ) ]
777+ public void Actions_Complexity_CompositeWinsOverlappingSimple_EvenWhenCompositeHasHigherPriorityField (
778+ ( string [ ] a1 , string [ ] a2 ) actions )
779+ {
780+ EnableComplexityShortcutResolution ( ) ;
781+
782+ var keyboard = InputSystem . AddDevice < Keyboard > ( ) ;
783+ var map = new InputActionMap ( "map" ) ;
784+
785+ var actionComposite = map . SetupTestAction ( actions . a1 ) ;
786+ var actionSimple = map . SetupTestAction ( actions . a2 ) ;
787+
788+ actionComposite . Priority = 100 ;
789+ actionSimple . Priority = 1 ;
790+
791+ map . Enable ( ) ;
792+
793+ PressBindingsForInputActions ( keyboard , actionComposite , actionSimple ) ;
794+
795+ Assert . That ( actionComposite . WasPerformedThisFrame ( ) , Is . True ) ;
796+ Assert . That ( actionSimple . WasPerformedThisFrame ( ) , Is . False ) ;
797+
798+ ReleaseBindingsForActions ( keyboard , actionComposite , actionSimple ) ;
799+ }
800+
801+ [ Test ]
802+ [ Category ( "Actions Priority" ) ]
803+ public void Actions_Complexity_BothSimpleActionsOnSameControlPerform_WhenEqualComplexity ( )
804+ {
805+ EnableComplexityShortcutResolution ( ) ;
806+
807+ var keyboard = InputSystem . AddDevice < Keyboard > ( ) ;
808+ var map = new InputActionMap ( "map" ) ;
809+ var action1 = map . AddAction ( "a" , binding : "<Keyboard>/y" ) ;
810+ var action2 = map . AddAction ( "b" , binding : "<Keyboard>/y" ) ;
811+ action1 . Priority = 2 ;
812+ action2 . Priority = 99 ;
813+ map . Enable ( ) ;
814+
815+ Press ( ( ButtonControl ) action1 . controls [ 0 ] , queueEventOnly : true ) ;
816+ Press ( ( ButtonControl ) action2 . controls [ 0 ] , queueEventOnly : true ) ;
817+ InputSystem . Update ( ) ;
818+
819+ Assert . That ( action1 . WasPerformedThisFrame ( ) , Is . True ) ;
820+ Assert . That ( action2 . WasPerformedThisFrame ( ) , Is . True ) ;
821+
822+ Release ( ( ButtonControl ) action1 . controls [ 0 ] , queueEventOnly : true ) ;
823+ Release ( ( ButtonControl ) action2 . controls [ 0 ] , queueEventOnly : true ) ;
824+ InputSystem . Update ( ) ;
825+ }
586826}
0 commit comments