Skip to content

Commit 3f46d65

Browse files
author
Rene Damm
authored
FIX: Enabling single actions leading to errors when enabling their map (#1061).
1 parent b575f89 commit 3f46d65

File tree

7 files changed

+165
-40
lines changed

7 files changed

+165
-40
lines changed

Assets/Tests/InputSystem/CoreTests_Actions.cs

Lines changed: 72 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,53 +1161,33 @@ public void Actions_CanByPassControlActuationChecks_UsingPasshtroughAction()
11611161
var action = new InputAction(type: InputActionType.PassThrough, binding: "<Gamepad>/*stick");
11621162
action.Enable();
11631163

1164-
using (var trace = new InputActionTrace())
1164+
using (var trace = new InputActionTrace(action))
11651165
{
1166-
trace.SubscribeTo(action);
1167-
11681166
Set(gamepad.leftStick, new Vector2(0.123f, 0.234f));
11691167

1170-
var actions = trace.ToArray();
1171-
Assert.That(actions, Has.Length.EqualTo(1));
1172-
Assert.That(actions[0].phase, Is.EqualTo(InputActionPhase.Performed));
1173-
Assert.That(actions[0].control, Is.SameAs(gamepad.leftStick));
1174-
Assert.That(actions[0].ReadValue<Vector2>(),
1175-
Is.EqualTo(new StickDeadzoneProcessor().Process(new Vector2(0.123f, 0.234f)))
1176-
.Using(Vector2EqualityComparer.Instance));
1168+
Assert.That(trace,
1169+
Performed(action, gamepad.leftStick, new StickDeadzoneProcessor().Process(new Vector2(0.123f, 0.234f))));
11771170

11781171
trace.Clear();
11791172

11801173
Set(gamepad.leftStick, new Vector2(0.234f, 0.345f));
11811174

1182-
actions = trace.ToArray();
1183-
Assert.That(actions, Has.Length.EqualTo(1));
1184-
Assert.That(actions[0].phase, Is.EqualTo(InputActionPhase.Performed));
1185-
Assert.That(actions[0].control, Is.SameAs(gamepad.leftStick));
1186-
Assert.That(actions[0].ReadValue<Vector2>(),
1187-
Is.EqualTo(new StickDeadzoneProcessor().Process(new Vector2(0.234f, 0.345f)))
1188-
.Using(Vector2EqualityComparer.Instance));
1175+
Assert.That(trace,
1176+
Performed(action, gamepad.leftStick, new StickDeadzoneProcessor().Process(new Vector2(0.234f, 0.345f))));
11891177

11901178
trace.Clear();
11911179

11921180
Set(gamepad.rightStick, new Vector2(0.123f, 0.234f));
11931181

1194-
actions = trace.ToArray();
1195-
Assert.That(actions, Has.Length.EqualTo(1));
1196-
Assert.That(actions[0].phase, Is.EqualTo(InputActionPhase.Performed));
1197-
Assert.That(actions[0].control, Is.SameAs(gamepad.rightStick));
1198-
Assert.That(actions[0].ReadValue<Vector2>(),
1199-
Is.EqualTo(new StickDeadzoneProcessor().Process(new Vector2(0.123f, 0.234f)))
1200-
.Using(Vector2EqualityComparer.Instance));
1182+
Assert.That(trace,
1183+
Performed(action, gamepad.rightStick, new StickDeadzoneProcessor().Process(new Vector2(0.123f, 0.234f))));
12011184

12021185
trace.Clear();
12031186

12041187
Set(gamepad.rightStick, Vector2.zero);
12051188

1206-
actions = trace.ToArray();
1207-
Assert.That(actions, Has.Length.EqualTo(1));
1208-
Assert.That(actions[0].phase, Is.EqualTo(InputActionPhase.Performed));
1209-
Assert.That(actions[0].control, Is.SameAs(gamepad.rightStick));
1210-
Assert.That(actions[0].ReadValue<Vector2>(), Is.EqualTo(Vector2.zero).Using(Vector2EqualityComparer.Instance));
1189+
Assert.That(trace,
1190+
Performed(action, gamepad.rightStick, Vector2.zero));
12111191
}
12121192
}
12131193

@@ -3588,6 +3568,69 @@ public void Actions_CanEnableActionThatHasNoControls()
35883568
Assert.That(action2.enabled, Is.True);
35893569
}
35903570

3571+
[Test]
3572+
[Category("Actions")]
3573+
public void Actions_CanMixEnablingSingleActionsAndEntireActionMaps()
3574+
{
3575+
var gamepad = InputSystem.AddDevice<Gamepad>();
3576+
3577+
var asset = ScriptableObject.CreateInstance<InputActionAsset>();
3578+
var map1 = new InputActionMap("map1");
3579+
var map2 = new InputActionMap("map2");
3580+
var action1 = map1.AddAction("action1", binding: "<Gamepad>/buttonSouth");
3581+
var action2 = map1.AddAction("action2", binding: "<Gamepad>/buttonNorth");
3582+
var action3 = map2.AddAction("action3", binding: "<Gamepad>/buttonSouth");
3583+
var action4 = map2.AddAction("action4", binding: "<Gamepad>/buttonNorth");
3584+
asset.AddActionMap(map1);
3585+
asset.AddActionMap(map2);
3586+
3587+
action3.Enable();
3588+
map1.Enable();
3589+
3590+
using (var trace1 = new InputActionTrace(action1))
3591+
using (var trace2 = new InputActionTrace(action2))
3592+
using (var trace3 = new InputActionTrace(action3))
3593+
using (var trace4 = new InputActionTrace(action4))
3594+
{
3595+
PressAndRelease(gamepad.buttonSouth);
3596+
3597+
Assert.That(trace1, Started(action1).AndThen(Performed(action1)).AndThen(Canceled(action1)));
3598+
Assert.That(trace2, Is.Empty);
3599+
Assert.That(trace3, Started(action3).AndThen(Performed(action3)).AndThen(Canceled(action3)));
3600+
Assert.That(trace4, Is.Empty);
3601+
3602+
trace1.Clear();
3603+
trace2.Clear();
3604+
trace3.Clear();
3605+
trace4.Clear();
3606+
3607+
map1.Disable();
3608+
map2.Enable();
3609+
3610+
PressAndRelease(gamepad.buttonSouth);
3611+
3612+
Assert.That(trace1, Is.Empty);
3613+
Assert.That(trace2, Is.Empty);
3614+
Assert.That(trace3, Started(action3).AndThen(Performed(action3)).AndThen(Canceled(action3)));
3615+
Assert.That(trace4, Is.Empty);
3616+
3617+
trace1.Clear();
3618+
trace2.Clear();
3619+
trace3.Clear();
3620+
trace4.Clear();
3621+
3622+
map1.Enable();
3623+
map2.Disable();
3624+
3625+
PressAndRelease(gamepad.buttonSouth);
3626+
3627+
Assert.That(trace1, Started(action1).AndThen(Performed(action1)).AndThen(Canceled(action1)));
3628+
Assert.That(trace2, Is.Empty);
3629+
Assert.That(trace3, Is.Empty);
3630+
Assert.That(trace4, Is.Empty);
3631+
}
3632+
}
3633+
35913634
[Test]
35923635
[Category("Actions")]
35933636
public void Actions_CanTargetSingleDeviceWithMultipleActions()
@@ -5724,7 +5767,6 @@ public void Actions_CompositesInDifferentMapsTiedToSameControlsWork()
57245767
asset.AddActionMap(map2);
57255768
asset.Enable();
57265769

5727-
57285770
InputControl performedControl1 = null;
57295771
InputControl performedControl2 = null;
57305772
action1.performed += ctx => performedControl1 = ctx.control;

Assets/Tests/InputSystem/CoreTests_Actions_Interactions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ internal partial class CoreTests
1212
[Preserve]
1313
class InteractionThatOnlyPerforms : IInputInteraction<float>
1414
{
15+
// Get rid of unused field warning.
16+
#pragma warning disable CS0649
1517
public bool stayPerformed;
18+
#pragma warning restore CS0649
1619

1720
public void Process(ref InputInteractionContext context)
1821
{

Assets/Tests/InputSystem/CoreTests_Editor.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2210,6 +2210,31 @@ public void Editor_InputEventsOccurringWhileGoingIntoPlayMode_AreDiscarded()
22102210
Assert.That(runtime.m_EventCount, Is.EqualTo(0));
22112211
}
22122212

2213+
[Test]
2214+
[Category("Editor")]
2215+
public void Editor_LeavingPlayMode_DestroysAllActionStates()
2216+
{
2217+
InputSystem.AddDevice<Gamepad>();
2218+
2219+
// Enter play mode.
2220+
InputSystem.OnPlayModeChange(PlayModeStateChange.ExitingEditMode);
2221+
InputSystem.OnPlayModeChange(PlayModeStateChange.EnteredPlayMode);
2222+
2223+
var action = new InputAction(binding: "<Gamepad>/buttonSouth");
2224+
action.Enable();
2225+
2226+
Assert.That(InputActionState.s_GlobalList.length, Is.EqualTo(1));
2227+
Assert.That(InputSystem.s_Manager.m_StateChangeMonitors.Length, Is.GreaterThan(0));
2228+
Assert.That(InputSystem.s_Manager.m_StateChangeMonitors[0].count, Is.EqualTo(1));
2229+
2230+
// Exit play mode.
2231+
InputSystem.OnPlayModeChange(PlayModeStateChange.ExitingPlayMode);
2232+
InputSystem.OnPlayModeChange(PlayModeStateChange.EnteredEditMode);
2233+
2234+
Assert.That(InputActionState.s_GlobalList.length, Is.Zero);
2235+
Assert.That(InputSystem.s_Manager.m_StateChangeMonitors[0].listeners[0].control, Is.Null); // Won't get removed, just cleared.
2236+
}
2237+
22132238
////TODO: tests for InputAssetImporter; for this we need C# mocks to be able to cut us off from the actual asset DB
22142239
}
22152240
#endif // UNITY_EDITOR

Packages/com.unity.inputsystem/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77
Due to package verification, the latest version below is the unpublished version and the date is meaningless.
88
however, it has to be formatted properly to pass verification tests.
99

10+
## [Unreleased]
11+
12+
### Fixed
13+
14+
#### Actions
15+
16+
- Mixing the enabling&disabling of single actions (as, for example, performed by `InputSystemUIInputModule`) with enabling&disabling of entire action maps (as, for example, performed by `PlayerInput`) no longer leaves to unresponsive input and `"should not reach here"` assertions ([forum thread](https://forum.unity.com/threads/error-while-switching-between-action-maps.825204/)).
17+
- Leaving play mode no longer leaves state change monitors lingering around from enabled actions.
18+
1019
## [1.0.0-preview.5] - 2020-02-14
1120

1221
### Changed

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

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ internal unsafe class InputActionState : IInputStateChangeMonitor, ICloneable, I
110110
public BindingState* bindingStates => memory.bindingStates;
111111
public InteractionState* interactionStates => memory.interactionStates;
112112
public int* controlIndexToBindingIndex => memory.controlIndexToBindingIndex;
113+
public int* enabledControls => memory.enabledControls;
113114

114115
private bool m_OnBeforeUpdateHooked;
115116
private bool m_OnAfterUpdateHooked;
@@ -158,6 +159,10 @@ private void Destroy(bool isFinalizing = false)
158159
{
159160
var map = maps[i];
160161

162+
// Remove state change monitors.
163+
if (map.enabled)
164+
DisableControls(i, mapIndices[i].controlStartIndex, mapIndices[i].controlCount);
165+
161166
if (map.m_Asset != null)
162167
map.m_Asset.m_SharedStateForAllMaps = null;
163168

@@ -467,17 +472,19 @@ public void EnableAllActions(InputActionMap map)
467472
Debug.Assert(map.m_Actions != null, "Map must have actions");
468473
Debug.Assert(maps.Contains(map), "Map must be contained in state");
469474

475+
// Enable all controls in map that aren't already enabled.
470476
EnableControls(map);
471477

472-
// Put all actions into waiting state.
478+
// Put all actions that aren't already enbaled into waiting state.
473479
var mapIndex = map.m_MapIndexInState;
474480
Debug.Assert(mapIndex >= 0 && mapIndex < totalMapCount, "Map index on InputActionMap is out of range");
475481
var actionCount = mapIndices[mapIndex].actionCount;
476482
var actionStartIndex = mapIndices[mapIndex].actionStartIndex;
477483
for (var i = 0; i < actionCount; ++i)
478484
{
479485
var actionIndex = actionStartIndex + i;
480-
actionStates[actionIndex].phase = InputActionPhase.Waiting;
486+
if (actionStates[actionIndex].isDisabled)
487+
actionStates[actionIndex].phase = InputActionPhase.Waiting;
481488
}
482489
map.m_EnabledActionsCount = actionCount;
483490

@@ -662,6 +669,7 @@ private void DisableControls(InputAction action)
662669

663670
////REVIEW: can we have a method on InputManager doing this in bulk?
664671

672+
////NOTE: This must not enable only a partial set of controls on a binding (currently we have no setup that would lead to that)
665673
private void EnableControls(int mapIndex, int controlStartIndex, int numControls)
666674
{
667675
Debug.Assert(controls != null, "State must have controls");
@@ -673,12 +681,20 @@ private void EnableControls(int mapIndex, int controlStartIndex, int numControls
673681
for (var i = 0; i < numControls; ++i)
674682
{
675683
var controlIndex = controlStartIndex + i;
684+
685+
// We don't want to add multiple state monitors for the same control. This can happen if enabling
686+
// single actions is mixed with enabling actions maps containing them.
687+
if (IsControlEnabled(controlIndex))
688+
continue;
689+
676690
var bindingIndex = controlIndexToBindingIndex[controlIndex];
677691
var mapControlAndBindingIndex = ToCombinedMapAndControlAndBindingIndex(mapIndex, controlIndex, bindingIndex);
678-
679-
if (bindingStates[bindingIndex].wantsInitialStateCheck)
680-
bindingStates[bindingIndex].initialStateCheckPending = true;
692+
var bindingStatePtr = &bindingStates[bindingIndex];
693+
if (bindingStatePtr->wantsInitialStateCheck)
694+
bindingStatePtr->initialStateCheckPending = true;
681695
manager.AddStateChangeMonitor(controls[controlIndex], this, mapControlAndBindingIndex);
696+
697+
SetControlEnabled(controlIndex, true);
682698
}
683699
}
684700

@@ -693,15 +709,38 @@ private void DisableControls(int mapIndex, int controlStartIndex, int numControl
693709
for (var i = 0; i < numControls; ++i)
694710
{
695711
var controlIndex = controlStartIndex + i;
712+
if (!IsControlEnabled(controlIndex))
713+
continue;
714+
696715
var bindingIndex = controlIndexToBindingIndex[controlIndex];
697716
var mapControlAndBindingIndex = ToCombinedMapAndControlAndBindingIndex(mapIndex, controlIndex, bindingIndex);
698-
699-
if (bindingStates[bindingIndex].wantsInitialStateCheck)
700-
bindingStates[bindingIndex].initialStateCheckPending = false;
717+
var bindingStatePtr = &bindingStates[bindingIndex];
718+
if (bindingStatePtr->wantsInitialStateCheck)
719+
bindingStatePtr->initialStateCheckPending = false;
701720
manager.RemoveStateChangeMonitor(controls[controlIndex], this, mapControlAndBindingIndex);
721+
722+
SetControlEnabled(controlIndex, false);
702723
}
703724
}
704725

726+
private bool IsControlEnabled(int controlIndex)
727+
{
728+
var intIndex = controlIndex / 32;
729+
var intMask = 1 << (controlIndex % 32);
730+
return (enabledControls[intIndex] & intMask) != 0;
731+
}
732+
733+
private void SetControlEnabled(int controlIndex, bool state)
734+
{
735+
var intIndex = controlIndex / 32;
736+
var intMask = 1 << (controlIndex % 32);
737+
738+
if (state)
739+
enabledControls[intIndex] |= intMask;
740+
else
741+
enabledControls[intIndex] &= ~intMask;
742+
}
743+
705744
private void HookOnBeforeUpdate()
706745
{
707746
if (m_OnBeforeUpdateHooked)
@@ -2860,7 +2899,8 @@ public struct UnmanagedMemory : IDisposable
28602899
compositeCount * sizeof(float) + // compositeMagnitudes
28612900
controlCount * sizeof(int) + // controlIndexToBindingIndex
28622901
actionCount * sizeof(ushort) * 2 + // actionBindingIndicesAndCounts
2863-
bindingCount * sizeof(ushort); // actionBindingIndices
2902+
bindingCount * sizeof(ushort) + // actionBindingIndices
2903+
(controlCount + 31) / 32 * sizeof(int); // enabledControlsArray
28642904

28652905
/// <summary>
28662906
/// Trigger state of all actions added to the state.
@@ -2903,6 +2943,8 @@ public struct UnmanagedMemory : IDisposable
29032943

29042944
public float* compositeMagnitudes;
29052945

2946+
public int* enabledControls;
2947+
29062948
/// <summary>
29072949
/// Array of pair of ints, one pair for each action (same index as <see cref="actionStates"/>). First int
29082950
/// is count of bindings on action, second int is index into <see cref="actionBindingIndices"/> where
@@ -2953,6 +2995,7 @@ public void Allocate(int mapCount, int actionCount, int bindingCount, int contro
29532995
controlIndexToBindingIndex = (int*)ptr; ptr += controlCount * sizeof(int);
29542996
actionBindingIndicesAndCounts = (ushort*)ptr; ptr += actionCount * sizeof(ushort) * 2;
29552997
actionBindingIndices = (ushort*)ptr; ptr += bindingCount * sizeof(ushort);
2998+
enabledControls = (int*)ptr; ptr += (controlCount + 31) / 32 * sizeof(int);
29562999
}
29573000

29583001
public void Dispose()
@@ -2997,6 +3040,7 @@ public void CopyDataFrom(UnmanagedMemory memory)
29973040
UnsafeUtility.MemCpy(controlIndexToBindingIndex, memory.controlIndexToBindingIndex, memory.controlCount * sizeof(int));
29983041
UnsafeUtility.MemCpy(actionBindingIndicesAndCounts, memory.actionBindingIndicesAndCounts, memory.actionCount * sizeof(ushort) * 2);
29993042
UnsafeUtility.MemCpy(actionBindingIndices, memory.actionBindingIndices, memory.bindingCount * sizeof(ushort));
3043+
UnsafeUtility.MemCpy(enabledControls, memory.enabledControls, (memory.controlCount + 31) / 32 * sizeof(int));
30003044
}
30013045

30023046
public UnmanagedMemory Clone()
@@ -3030,7 +3074,7 @@ public UnmanagedMemory Clone()
30303074
///
30313075
/// Both of these needs are served by this global list.
30323076
/// </remarks>
3033-
private static InlinedArray<GCHandle> s_GlobalList;
3077+
internal static InlinedArray<GCHandle> s_GlobalList;
30343078
internal static InlinedArray<Action<object, InputActionChange>> s_OnActionChange;
30353079

30363080
private void AddToGlobaList()

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1130,6 +1130,7 @@ public void AddDevice(InputDevice device)
11301130
}
11311131

11321132
////TODO: this path should really put the device on the list of available devices
1133+
////TODO: this path should discover disconnected devices
11331134
public InputDevice AddDevice(InputDeviceDescription description)
11341135
{
11351136
////REVIEW: is throwing here really such a useful thing?

Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/PlayerInput.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ public bool neverAutoSwitchControlSchemes
407407
}
408408
}
409409

410+
////REVIEW: this is inconsistent; currentControlScheme is a string, this is an InputActionMap
410411
/// <summary>
411412
/// The currently enabled action map.
412413
/// </summary>

0 commit comments

Comments
 (0)