Skip to content

Commit 22d5cf7

Browse files
Add a small refactor to InputActionState & some tests for the area.
1 parent 848b486 commit 22d5cf7

3 files changed

Lines changed: 163 additions & 47 deletions

File tree

Assets/Tests/InputSystem/CoreTests_ActionsPriority.cs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,4 +417,126 @@ public void AltShiftW_Only_Triggers_TeamChat(ThreeInputActionDataWrapper<InputAc
417417
// Release(keyboard.leftShiftKey);
418418
// Release(keyboard.altKey);
419419
}
420+
421+
[Test]
422+
[Category("Actions Priority")]
423+
public void Actions_Priority_ControlGroupingTable_StrideAndElementIndicesMatchInterleavedLayout()
424+
{
425+
Assert.That(InputActionState.ControlGroupingTable.Stride, Is.EqualTo(2));
426+
Assert.That(InputActionState.ControlGroupingTable.GroupElementIndex(3), Is.EqualTo(6));
427+
Assert.That(InputActionState.ControlGroupingTable.PriorityElementIndex(3), Is.EqualTo(7));
428+
}
429+
430+
[Test]
431+
[Category("Actions Priority")]
432+
public void Actions_Priority_InputActionStateMonitorIndex_RoundTripsComponents()
433+
{
434+
var index = InputActionStateMonitorIndex.Create(mapIndex: 7, controlIndex: 0x00abcdef, bindingIndex: 0x0bcd,
435+
priority: 200);
436+
437+
Assert.That(index.MapIndex, Is.EqualTo(7));
438+
Assert.That(index.ControlIndex, Is.EqualTo(0x00abcdef));
439+
Assert.That(index.BindingIndex, Is.EqualTo(0x0bcd));
440+
Assert.That(index.Priority, Is.EqualTo(200));
441+
}
442+
443+
[Test]
444+
[Category("Actions Priority")]
445+
public void Actions_Priority_InputActionStateMonitorIndex_FromPacked_MatchesCreateOutput()
446+
{
447+
var created = InputActionStateMonitorIndex.Create(3, 100, 200, 42);
448+
var roundTrip = InputActionStateMonitorIndex.FromPacked(created.Packed);
449+
450+
Assert.That(roundTrip.MapIndex, Is.EqualTo(created.MapIndex));
451+
Assert.That(roundTrip.ControlIndex, Is.EqualTo(created.ControlIndex));
452+
Assert.That(roundTrip.BindingIndex, Is.EqualTo(created.BindingIndex));
453+
Assert.That(roundTrip.Priority, Is.EqualTo(created.Priority));
454+
}
455+
456+
[Test]
457+
[Category("Actions Priority")]
458+
public void Actions_Priority_InputActionStateMonitorIndex_PriorityUsesLowEightBitsInPackedRepresentation()
459+
{
460+
var index = InputActionStateMonitorIndex.Create(0, 1, 0, priority: 300);
461+
Assert.That(index.Priority, Is.EqualTo(300 & 0xff));
462+
}
463+
464+
[Test]
465+
[Category("Actions Priority")]
466+
public void Actions_Priority_InputActionStateMonitorIndex_ImplicitConversionToLongMatchesPackedProperty()
467+
{
468+
var index = InputActionStateMonitorIndex.Create(1, 2, 3, 4);
469+
long asLong = index;
470+
Assert.That(asLong, Is.EqualTo(index.Packed));
471+
}
472+
473+
[Test]
474+
[Category("Actions Priority")]
475+
public unsafe void Actions_Priority_ControlGrouping_SamePhysicalControlSharesGroupId()
476+
{
477+
var keyboard = InputSystem.AddDevice<Keyboard>();
478+
var map = new InputActionMap("priority_group_test");
479+
map.AddAction("a", binding: "<Keyboard>/z");
480+
map.AddAction("b", binding: "<Keyboard>/z");
481+
map.Enable();
482+
483+
var state = map.m_State;
484+
Assert.That(state, Is.Not.Null);
485+
Assert.That(state.memory.controlGroupingInitialized, Is.True);
486+
487+
for (var i = 0; i < state.totalControlCount; ++i)
488+
{
489+
for (var j = i + 1; j < state.totalControlCount; ++j)
490+
{
491+
if (state.controls[i] != state.controls[j])
492+
continue;
493+
494+
var gi = InputActionState.ControlGroupingTable.GroupElementIndex(i);
495+
var gj = InputActionState.ControlGroupingTable.GroupElementIndex(j);
496+
Assert.That(state.memory.controlGroupingAndPriority[gi], Is.EqualTo(state.memory.controlGroupingAndPriority[gj]));
497+
Assert.That(state.memory.controlGroupingAndPriority[gi], Is.Not.EqualTo(0));
498+
return;
499+
}
500+
}
501+
502+
Assert.Fail("Expected two control slots bound to the same physical control.");
503+
}
504+
505+
[Test]
506+
[Category("Actions Priority")]
507+
public unsafe void Actions_Priority_ControlGrouping_WritesPerControlSlotPriorityFromAction()
508+
{
509+
var keyboard = InputSystem.AddDevice<Keyboard>();
510+
var map = new InputActionMap("priority_per_slot_test");
511+
var actionLow = map.AddAction("low", binding: "<Keyboard>/x");
512+
var actionHigh = map.AddAction("high", binding: "<Keyboard>/x");
513+
actionLow.Priority = 4;
514+
actionHigh.Priority = 11;
515+
map.Enable();
516+
517+
var state = map.m_State;
518+
Assert.That(state, Is.Not.Null);
519+
520+
var lowIndex = -1;
521+
var highIndex = -1;
522+
for (var i = 0; i < state.totalControlCount; ++i)
523+
{
524+
if (state.controls[i] != keyboard.xKey)
525+
continue;
526+
var bindingIndex = state.controlIndexToBindingIndex[i];
527+
var actionIndex = state.bindingStates[bindingIndex].actionIndex;
528+
if (actionIndex == actionLow.m_ActionIndexInState)
529+
lowIndex = i;
530+
else if (actionIndex == actionHigh.m_ActionIndexInState)
531+
highIndex = i;
532+
}
533+
534+
Assert.That(lowIndex, Is.GreaterThanOrEqualTo(0));
535+
Assert.That(highIndex, Is.GreaterThanOrEqualTo(0));
536+
537+
var pLow = InputActionState.ControlGroupingTable.PriorityElementIndex(lowIndex);
538+
var pHigh = InputActionState.ControlGroupingTable.PriorityElementIndex(highIndex);
539+
Assert.That(state.memory.controlGroupingAndPriority[pLow], Is.EqualTo(4));
540+
Assert.That(state.memory.controlGroupingAndPriority[pHigh], Is.EqualTo(11));
541+
}
420542
}

Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs

Lines changed: 37 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,26 @@ internal unsafe class InputActionState : IInputStateChangeMonitor, ICloneable, I
112112
public BindingState* bindingStates => memory.bindingStates;
113113
public InteractionState* interactionStates => memory.interactionStates;
114114
public int* controlIndexToBindingIndex => memory.controlIndexToBindingIndex;
115-
public ushort* controlGroupingAndPriority => memory.controlGroupingAndPriority;
115+
private ushort* controlGroupingAndPriority => memory.controlGroupingAndPriority;
116+
117+
/// <summary>
118+
/// Layout of <see cref="UnmanagedMemory.controlGroupingAndPriority"/>: interleaved ushort pairs (group id, binding priority) per control slot.
119+
/// </summary>
120+
internal static class ControlGroupingTable
121+
{
122+
public const int Stride = 2;
123+
124+
public static int GroupElementIndex(int controlIndex) => controlIndex * Stride;
125+
126+
public static int PriorityElementIndex(int controlIndex) => controlIndex * Stride + 1;
127+
}
128+
129+
private uint GetControlMonitorGroupIndex(int controlIndex) =>
130+
controlGroupingAndPriority[ControlGroupingTable.GroupElementIndex(controlIndex)];
131+
132+
private int GetControlBindingPriority(int controlIndex) =>
133+
controlGroupingAndPriority[ControlGroupingTable.PriorityElementIndex(controlIndex)];
134+
116135
public float* controlMagnitudes => memory.controlMagnitudes;
117136
public uint* enabledControls => (uint*)memory.enabledControls;
118137

@@ -160,10 +179,10 @@ private void ComputeControlGroupingIfNecessary()
160179

161180
var priority = Math.Clamp(action != null ? action.Priority : 0, 0, 65535);
162181

163-
controlGroupingAndPriority[i * 2 + 1] = (ushort)priority;
182+
controlGroupingAndPriority[ControlGroupingTable.PriorityElementIndex(i)] = (ushort)priority;
164183

165184
// Compute grouping. If already set, skip.
166-
if (controlGroupingAndPriority[i * 2] == 0)
185+
if (controlGroupingAndPriority[ControlGroupingTable.GroupElementIndex(i)] == 0)
167186
{
168187
for (var n = 0; n < totalControlCount; ++n)
169188
{
@@ -176,10 +195,10 @@ private void ComputeControlGroupingIfNecessary()
176195
if (control != otherControl)
177196
continue;
178197

179-
controlGroupingAndPriority[n * 2] = (ushort)currentGroup;
198+
controlGroupingAndPriority[ControlGroupingTable.GroupElementIndex(n)] = (ushort)currentGroup;
180199
}
181200

182-
controlGroupingAndPriority[i * 2] = (ushort)currentGroup;
201+
controlGroupingAndPriority[ControlGroupingTable.GroupElementIndex(i)] = (ushort)currentGroup;
183202

184203
++currentGroup;
185204
}
@@ -1150,7 +1169,8 @@ private void EnableControls(int mapIndex, int controlStartIndex, int numControls
11501169
var bindingStatePtr = &bindingStates[bindingIndex];
11511170
if (bindingStatePtr->wantsInitialStateCheck)
11521171
SetInitialStateCheckPending(bindingStatePtr, true);
1153-
manager.AddStateChangeMonitor(controls[controlIndex], this, mapControlAndBindingIndex, controlGroupingAndPriority[controlIndex * 2]);
1172+
manager.AddStateChangeMonitor(controls[controlIndex], this, mapControlAndBindingIndex,
1173+
GetControlMonitorGroupIndex(controlIndex));
11541174

11551175
SetControlEnabled(controlIndex, true);
11561176
}
@@ -1344,19 +1364,21 @@ void IInputStateChangeMonitor.NotifyControlStateChanged(InputControl control, do
13441364
return;
13451365
#endif
13461366

1347-
SplitUpMapAndControlAndBindingIndex(mapControlAndBindingIndex, out var mapIndex, out var controlIndex, out var bindingIndex);
1348-
ProcessControlStateChange(mapIndex, controlIndex, bindingIndex, time, eventPtr);
1367+
var monitorIndex = InputActionStateMonitorIndex.FromPacked(mapControlAndBindingIndex);
1368+
ProcessControlStateChange(monitorIndex.MapIndex, monitorIndex.ControlIndex, monitorIndex.BindingIndex, time,
1369+
eventPtr);
13491370
}
13501371

13511372
void IInputStateChangeMonitor.NotifyTimerExpired(InputControl control, double time,
13521373
long mapControlAndBindingIndex, int interactionIndex)
13531374
{
1354-
SplitUpMapAndControlAndBindingIndex(mapControlAndBindingIndex, out var mapIndex, out var controlIndex, out var bindingIndex);
1355-
ProcessTimeout(time, mapIndex, controlIndex, bindingIndex, interactionIndex);
1375+
var monitorIndex = InputActionStateMonitorIndex.FromPacked(mapControlAndBindingIndex);
1376+
ProcessTimeout(time, monitorIndex.MapIndex, monitorIndex.ControlIndex, monitorIndex.BindingIndex,
1377+
interactionIndex);
13561378
}
13571379

13581380
/// <summary>
1359-
/// Bit pack the mapIndex, controlIndex, bindingIndex and complexity components into a single long monitor index value.
1381+
/// Bit pack the mapIndex, controlIndex, bindingIndex and binding-priority components into a single long monitor index value.
13601382
/// </summary>
13611383
/// <param name="mapIndex">The mapIndex value to pack.</param>
13621384
/// <param name="controlIndex">The controlIndex value to pack.</param>
@@ -1366,42 +1388,14 @@ void IInputStateChangeMonitor.NotifyTimerExpired(InputControl control, double ti
13661388
/// monitors. While we could look up map and binding indices from control indices, keeping
13671389
/// all the information together avoids having to unnecessarily jump around in memory to grab
13681390
/// the various pieces of data.
1369-
/// The complexity component is implicitly derived and does not need to be passed as an argument.
1391+
/// Priority is read from <see cref="ControlGroupingTable"/> data for <paramref name="controlIndex"/>.
13701392
/// </remarks>
13711393
private long ToCombinedMapAndControlAndBindingIndex(int mapIndex, int controlIndex, int bindingIndex)
13721394
{
13731395
// We have limits on the numbers of maps, controls, and bindings we allow in any single
13741396
// action state (see TriggerState.kMaxNumXXX).
1375-
var complexity = controlGroupingAndPriority[controlIndex * 2 + 1];
1376-
var result = (long)controlIndex;
1377-
result |= (long)bindingIndex << 24;
1378-
result |= (long)mapIndex << 40;
1379-
result |= (long)complexity << 48;
1380-
return result;
1381-
}
1382-
1383-
/// <summary>
1384-
/// Extract the mapIndex, controlIndex and bindingIndex components from the provided bit packed argument (monitor index).
1385-
/// </summary>
1386-
/// <param name="mapControlAndBindingIndex">Represents a monitor index, which is a bit packed field containing multiple components.</param>
1387-
/// <param name="mapIndex">Will hold the extracted mapIndex value after the function completes.</param>
1388-
/// <param name="controlIndex">Will hold the extracted controlIndex value after the function completes.</param>
1389-
/// <param name="bindingIndex">Will hold the extracted bindingIndex value after the function completes.</param>
1390-
private void SplitUpMapAndControlAndBindingIndex(long mapControlAndBindingIndex, out int mapIndex,
1391-
out int controlIndex, out int bindingIndex)
1392-
{
1393-
controlIndex = (int)(mapControlAndBindingIndex & 0x00ffffff);
1394-
bindingIndex = (int)((mapControlAndBindingIndex >> 24) & 0xffff);
1395-
mapIndex = (int)((mapControlAndBindingIndex >> 40) & 0xff);
1396-
}
1397-
1398-
/// <summary>
1399-
/// Extract the 'complexity' component from the provided bit packed argument (monitor index).
1400-
/// </summary>
1401-
/// <param name="mapControlAndBindingIndex">Represents a monitor index, which is a bit packed field containing multiple components.</param>
1402-
internal static int GetPriorityFromMonitorIndex(long mapControlAndBindingIndex)
1403-
{
1404-
return (int)((mapControlAndBindingIndex >> 48) & 0xff);
1397+
return InputActionStateMonitorIndex.Create(mapIndex, controlIndex, bindingIndex,
1398+
GetControlBindingPriority(controlIndex)).Packed;
14051399
}
14061400

14071401
/// <summary>
@@ -2462,7 +2456,7 @@ private void ChangePhaseOfActionInternal(int actionIndex, TriggerState* actionSt
24622456
// When we perform an action, we mark the event handled such that FireStateChangeNotifications()
24632457
// can then reset state monitors in the same group (strictly lower binding priority only).
24642458
// NOTE: We don't consume for controls at binding priority 0. Those we fire in unison.
2465-
if (controlGroupingAndPriority[trigger.controlIndex * 2 + 1] > 0 &&
2459+
if (GetControlBindingPriority(trigger.controlIndex) > 0 &&
24662460
// we can end up switching to performed state from an interaction with a timeout, at which point
24672461
// the original event will probably have been removed from memory, so make sure to check
24682462
// we still have one

Packages/com.unity.inputsystem/InputSystem/InputManagerStateMonitors.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -270,12 +270,12 @@ internal unsafe void FireStateChangeNotifications(int deviceIndex, double intern
270270
if (!previouslyHandled && eventPtr->handled)
271271
{
272272
var groupIndex = listeners[i].groupIndex;
273-
var handlerPriority = InputActionState.GetPriorityFromMonitorIndex(listener.monitorIndex);
273+
var handlerPriority = InputActionStateMonitorIndex.FromPacked(listener.monitorIndex).Priority;
274274
for (var n = i + 1; n < signals.length; ++n)
275275
{
276276
if (listeners[n].groupIndex == groupIndex && listeners[n].monitor == listener.monitor)
277277
{
278-
var candidatePriority = InputActionState.GetPriorityFromMonitorIndex(listeners[n].monitorIndex);
278+
var candidatePriority = InputActionStateMonitorIndex.FromPacked(listeners[n].monitorIndex).Priority;
279279
if (candidatePriority < handlerPriority)
280280
signals.ClearBit(n);
281281
}
@@ -412,8 +412,8 @@ public void SortMonitorsByIndex()
412412
{
413413
for (var j = i; j > 0; --j)
414414
{
415-
var firstPriority = InputActionState.GetPriorityFromMonitorIndex(listeners[j - 1].monitorIndex);
416-
var secondPriority = InputActionState.GetPriorityFromMonitorIndex(listeners[j].monitorIndex);
415+
var firstPriority = InputActionStateMonitorIndex.FromPacked(listeners[j - 1].monitorIndex).Priority;
416+
var secondPriority = InputActionStateMonitorIndex.FromPacked(listeners[j].monitorIndex).Priority;
417417
if (firstPriority >= secondPriority)
418418
break;
419419

0 commit comments

Comments
 (0)