Skip to content

Commit 585047c

Browse files
author
Rene Damm
authored
FIX: Drag-reordering actions changing IDs (case 1231233, #1118).
1 parent e80dda7 commit 585047c

File tree

6 files changed

+129
-77
lines changed

6 files changed

+129
-77
lines changed

Assets/Tests/InputSystem/CoreTests_Editor.cs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,82 @@ public void Editor_InputAsset_CanAddAndRemoveActionMapThroughSerialization()
334334
Assert.That(asset.actionMaps[0].name, Is.EqualTo(actionMap2Name));
335335
}
336336

337+
[Test]
338+
[Category("Editor")]
339+
public void Editor_InputAsset_CanAddAndRemoveElementThroughSerialization()
340+
{
341+
var map = new InputActionMap("map");
342+
var action1 = map.AddAction(name: "action1", binding: "<Gamepad>/leftStick");
343+
var action2 = map.AddAction(name: "action2", binding: "<Gamepad>/rightStick");
344+
action2.AddBinding("<Gamepad>/dpad");
345+
var asset = ScriptableObject.CreateInstance<InputActionAsset>();
346+
asset.AddActionMap(map);
347+
348+
var mapId = map.id;
349+
var action1Id = action1.id;
350+
var action2Id = action2.id;
351+
var binding1Id = map.bindings[0].id;
352+
var binding2Id = map.bindings[1].id;
353+
var binding3Id = map.bindings[2].id;
354+
355+
var obj = new SerializedObject(asset);
356+
357+
var maps = obj.FindProperty("m_ActionMaps");
358+
InputActionSerializationHelpers.AddElement(maps, "new map", 0);
359+
360+
var actions = obj.FindProperty("m_ActionMaps").GetArrayElementAtIndex(1).FindPropertyRelative("m_Actions");
361+
var bindings = obj.FindProperty("m_ActionMaps").GetArrayElementAtIndex(1).FindPropertyRelative("m_Bindings");
362+
InputActionSerializationHelpers.AddElement(actions, "new action", 1);
363+
InputActionSerializationHelpers.AddElement(bindings, "new binding", 1);
364+
365+
obj.ApplyModifiedPropertiesWithoutUndo();
366+
367+
// By the nature of Unity serialization, only the connection to UnityEngine.Objects is maintained
368+
// for C# objects. So map, action1, and action2 are all no longer the objects inside the asset.
369+
map = asset.actionMaps[1];
370+
371+
Assert.That(asset.actionMaps.Count, Is.EqualTo(2));
372+
Assert.That(asset.actionMaps[0].name, Is.EqualTo("new map"));
373+
Assert.That(asset.actionMaps[1].name, Is.EqualTo("map"));
374+
Assert.That(asset.actionMaps[0].id, Is.Not.EqualTo(mapId));
375+
Assert.That(asset.actionMaps[1].id, Is.EqualTo(mapId));
376+
377+
Assert.That(map.actions, Has.Count.EqualTo(3));
378+
Assert.That(map.actions[0].name, Is.EqualTo("action1"));
379+
Assert.That(map.actions[1].name, Is.EqualTo("new action"));
380+
Assert.That(map.actions[2].name, Is.EqualTo("action2"));
381+
Assert.That(map.actions[0].id, Is.EqualTo(action1Id));
382+
Assert.That(map.actions[1].id, Is.Not.EqualTo(action1Id));
383+
Assert.That(map.actions[1].id, Is.Not.EqualTo(action2Id));
384+
Assert.That(map.actions[2].id, Is.EqualTo(action2Id));
385+
Assert.That(map.actions[0].bindings, Has.Count.EqualTo(1));
386+
Assert.That(map.actions[1].bindings, Has.Count.Zero);
387+
Assert.That(map.actions[2].bindings, Has.Count.EqualTo(2));
388+
Assert.That(map.actions[0].bindings[0].path, Is.EqualTo("<Gamepad>/leftStick"));
389+
Assert.That(map.actions[2].bindings[0].path, Is.EqualTo("<Gamepad>/rightStick"));
390+
Assert.That(map.actions[2].bindings[1].path, Is.EqualTo("<Gamepad>/dpad"));
391+
392+
Assert.That(map.bindings, Has.Count.EqualTo(4));
393+
Assert.That(map.bindings[0].id, Is.EqualTo(binding1Id));
394+
Assert.That(map.bindings[1].id, Is.Not.EqualTo(binding1Id));
395+
Assert.That(map.bindings[1].id, Is.Not.EqualTo(binding2Id));
396+
Assert.That(map.bindings[1].id, Is.Not.EqualTo(binding3Id));
397+
Assert.That(map.bindings[2].id, Is.EqualTo(binding2Id));
398+
Assert.That(map.bindings[3].id, Is.EqualTo(binding3Id));
399+
Assert.That(map.bindings[0].name, Is.Not.EqualTo("new binding"));
400+
Assert.That(map.bindings[1].name, Is.EqualTo("new binding"));
401+
Assert.That(map.bindings[2].name, Is.Not.EqualTo("new binding"));
402+
Assert.That(map.bindings[3].name, Is.Not.EqualTo("new binding"));
403+
Assert.That(map.bindings[0].path, Is.EqualTo("<Gamepad>/leftStick"));
404+
Assert.That(map.bindings[1].path, Is.Empty);
405+
Assert.That(map.bindings[2].path, Is.EqualTo("<Gamepad>/rightStick"));
406+
Assert.That(map.bindings[3].path, Is.EqualTo("<Gamepad>/dpad"));
407+
Assert.That(map.bindings[0].action, Is.EqualTo("action1"));
408+
Assert.That(map.bindings[1].action, Is.Empty);
409+
Assert.That(map.bindings[2].action, Is.EqualTo("action2"));
410+
Assert.That(map.bindings[3].action, Is.EqualTo("action2"));
411+
}
412+
337413
[Test]
338414
[Category("Editor")]
339415
public void Editor_InputAsset_CanAddAndRemoveActionThroughSerialization()

Packages/com.unity.inputsystem/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ however, it has to be formatted properly to pass verification tests.
3333
- The importer for `.inputactions` assets will now check out from version control the generated .cs file when overwriting it &ndash; which only happens if the contents differ ([case 1222972](https://issuetracker.unity3d.com/issues/inputsystem-editor-generated-c-number-file-is-not-checked-out-when-overwriting)).
3434
- The editor for `.inputactions` assets will now check out from version control the asset before saving it.
3535
- Drag-reordering action maps no longer throws "Should have drop target" asserts in the console (case [1229146](https://issuetracker.unity3d.com/issues/inputsystem-reordering-of-actionmaps-in-input-action-window-fails-and-throws-should-have-drop-target-error)).
36+
- Drag-reordering actions no longer changes action IDs of some of the existing actions ([case 1231233](https://issuetracker.unity3d.com/issues/input-systems-action-ids-dont-stick-with-action-names-when-input-actions-are-reorganized)).
3637
- References to `InputActionReference` objects created by the importer for `.inputactions` files are no longer broken when the action referenced by the object is renamed ([case 1229145](https://issuetracker.unity3d.com/issues/inputsystem-inputactionreference-loses-guid-when-its-action-is-moved-or-renamed-in-the-inputaction-asset)).
3738
* __NOTE: This fix does not apply to existing `InputActionReference` instances.__ The problem was inherent in the internal file IDs generated for actions &ndash; which were affected by action and map names. Thus, changing the name of an action or map would change the resulting file ID of the `InputActionReference`.<br>However, changing file IDs will break any existing reference to the object. Thus we had to preserve the existing `InputActionReference` objects under their original file ID. We hide them in the Project Browser, however. The ones that are visible now have the new, fixed file IDs.<br>To switch existing `InputActionReference` properties to the new file IDs, simply replace them with the newly created `InputActionReference`.
3839

Packages/com.unity.inputsystem/Documentation~/KnownLimitations.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,23 @@ The following is a list of known limitations that the Input System currently has
44

55
## Compatibility with other Unity features
66

7+
* Input processing in the background is tied to `Application.runInBackground` (i.e. the "Run In Background" setting in "Player Preferences") which, however, Unity always forces to `true` in __development__ players. This means that in development players, input will always be processed, even if the app is in the background. Of course, this only pertains to platforms where the player can actually run in the background (iOS and Android are thus unaffected).
78
* `PlayerInput` split-screen support does not work with Cinemachine virtual cameras.
8-
* The Input System cannot generate input for IMGUI or UIElements.
9+
* The Input System cannot generate input for IMGUI or UIElements. Support for the latter is being worked on.
910
* The Input System does not yet support the new 2019.3 mode where domain reloads are disabled when entering play mode.
1011

1112
### uGUI
1213

1314
* After enabling, the UI will not react to a pointer's position until the position is changed.
15+
* The new input system cannot yet feed text input into uGUI and TextMesh Pro input field components. This means that text input ATM is still picked up directly and internally from the Unity native runtime.
1416

1517
## Device support
1618

1719
* (Desktop) We do not yet support distinguishing input from multiple pointers (mouse, pen, touch) or keyboards. There will be a single Mouse, Pen, Touch, and Keyboard device.
18-
* (Windows) Mouse input from remote desktop connections does not come through properly.
1920
* (Windows) Pen input will not work with Wacom devices if "Windows Ink" support is turned off.
21+
* (Windows) HID input is not currently supported in 32-bit players. This means that devices such as the PS4 controller will not work in 32-bit standalone players. Use the 64-bit standalone player instead.
2022
* (Android) We only support a single Touchscreen at the moment.
23+
* (Stadia) The Stadia controller is only supported __in__ the Stadia player at the moment. In the editor, use the generic `Gamepad` for bindings and use any Xbox or PS4 controller for testing.
2124
* Joy-Cons are only supported on Switch.
2225
* Sensors in the PS4 controller are currently only supported on PS4.
2326
>NOTE: Support for NDA platforms is distributed as separate packages due to licensing restrictions. The packages, at this point, are made available separately to licensees for download and installation.

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

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,19 @@
1111
//// If it mentions the action, it appears on the action. Otherwise it doesn't. The controls should consistently appear on the
1212
//// action based on what action the *composite* references.
1313

14+
////REVIEW: Should we bring the checkboxes for actions back? We tried to "simplify" things by collapsing everything into a InputActionTypes
15+
//// and making the various behavior toggles implicit in that. However, my impression is that this has largely backfired by making
16+
//// it opaque what the choices actually entail and by giving no way out if the choices for one reason or another don't work out
17+
//// perfectly.
18+
////
19+
//// My impression is that at least two the following two checkboxes would make sense:
20+
//// 1) Initial State Check? Whether the action should immediately sync to the current state of controls when enabled.
21+
//// 2) Resolve Conflicting Inputs? Whether the action should try to resolve conflicts between multiple concurrent inputs.
22+
////
23+
//// I'm fine hiding this under an "Advanced" foldout or something. But IMO, control over this should be available to the user.
24+
////
25+
//// In the same vein, we probably also should expose control over how an action behaves on focus loss (https://forum.unity.com/threads/actions-canceled-when-game-loses-focus.855217/).
26+
1427
////REVIEW: I think the action system as it is today offers too many ways to shoot yourself in the foot. It has
1528
//// flexibility but at the same time has abundant opportunity for ending up with dysfunction. Common setups
1629
//// have to come preconfigured and work robustly for the user without requiring much understanding of how
@@ -188,17 +201,17 @@ public Guid id
188201
get
189202
{
190203
MakeSureIdIsInPlace();
191-
return m_Guid;
204+
return new Guid(m_Id);
192205
}
193206
}
194207

195208
internal Guid idDontGenerate
196209
{
197210
get
198211
{
199-
if (m_Guid == Guid.Empty && !string.IsNullOrEmpty(m_Id))
200-
m_Guid = new Guid(m_Id);
201-
return m_Guid;
212+
if (string.IsNullOrEmpty(m_Id))
213+
return default;
214+
return new Guid(m_Id);
202215
}
203216
}
204217

@@ -973,7 +986,6 @@ public unsafe object ReadValueAsObject()
973986
[NonSerialized] internal int m_BindingsCount;
974987
[NonSerialized] internal int m_ControlStartIndex;
975988
[NonSerialized] internal int m_ControlCount;
976-
[NonSerialized] internal Guid m_Guid;
977989

978990
/// <summary>
979991
/// Index of the action in the <see cref="InputActionState"/> associated with the
@@ -1022,21 +1034,14 @@ private InputActionState.TriggerState currentState
10221034

10231035
internal string MakeSureIdIsInPlace()
10241036
{
1025-
if (m_Guid != Guid.Empty)
1026-
return m_Id;
1027-
10281037
if (string.IsNullOrEmpty(m_Id))
10291038
GenerateId();
1030-
else
1031-
m_Guid = new Guid(m_Id);
1032-
10331039
return m_Id;
10341040
}
10351041

10361042
internal void GenerateId()
10371043
{
1038-
m_Guid = Guid.NewGuid();
1039-
m_Id = m_Guid.ToString();
1044+
m_Id = Guid.NewGuid().ToString();
10401045
}
10411046

10421047
internal InputActionMap GetOrCreateActionMap()

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

Lines changed: 23 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -100,28 +100,19 @@ public Guid id
100100
{
101101
get
102102
{
103-
if (m_Guid == Guid.Empty)
104-
{
105-
if (m_Id == null)
106-
{
107-
GenerateId();
108-
}
109-
else
110-
{
111-
m_Guid = new Guid(m_Id);
112-
}
113-
}
114-
return m_Guid;
103+
if (string.IsNullOrEmpty(m_Id))
104+
GenerateId();
105+
return new Guid(m_Id);
115106
}
116107
}
117108

118109
internal Guid idDontGenerate
119110
{
120111
get
121112
{
122-
if (m_Guid == Guid.Empty && !string.IsNullOrEmpty(m_Id))
123-
m_Guid = new Guid(m_Id);
124-
return m_Guid;
113+
if (string.IsNullOrEmpty(m_Id))
114+
return default;
115+
return new Guid(m_Id);
125116
}
126117
}
127118

@@ -405,23 +396,28 @@ internal int FindActionIndex(string nameOrId)
405396

406397
if (string.IsNullOrEmpty(nameOrId))
407398
return -1;
408-
409399
if (m_Actions == null)
410-
return InputActionState.kInvalidIndex;
400+
return -1;
401+
411402
var actionCount = m_Actions.Length;
412403

413-
// If it contains hyphens, it may be a GUID so try looking up that way.
414-
if (nameOrId.Contains('-') && Guid.TryParse(nameOrId, out var id))
404+
var isOldBracedFormat = nameOrId.StartsWith("{") && nameOrId.EndsWith("}");
405+
if (isOldBracedFormat)
415406
{
407+
var length = nameOrId.Length - 2;
416408
for (var i = 0; i < actionCount; ++i)
417-
if (m_Actions[i].idDontGenerate == id)
409+
{
410+
if (string.Compare(m_Actions[i].m_Id, 0, nameOrId, 1, length) == 0)
418411
return i;
412+
}
419413
}
420414

421-
// Default search goes by name (case insensitive).
422415
for (var i = 0; i < actionCount; ++i)
423-
if (string.Compare(m_Actions[i].m_Name, nameOrId, StringComparison.InvariantCultureIgnoreCase) == 0)
416+
{
417+
var action = m_Actions[i];
418+
if (action.m_Id == nameOrId || string.Compare(m_Actions[i].m_Name, nameOrId, StringComparison.InvariantCultureIgnoreCase) == 0)
424419
return i;
420+
}
425421

426422
return InputActionState.kInvalidIndex;
427423
}
@@ -616,10 +612,7 @@ public InputActionMap Clone()
616612
var bindings = new InputBinding[bindingCount];
617613
Array.Copy(m_Bindings, 0, bindings, 0, bindingCount);
618614
for (var i = 0; i < bindingCount; ++i)
619-
{
620615
bindings[i].m_Id = default;
621-
bindings[i].m_Guid = default;
622-
}
623616
clone.m_Bindings = bindings;
624617
}
625618

@@ -727,11 +720,6 @@ IEnumerator IEnumerable.GetEnumerator()
727720
/// </remarks>
728721
[NonSerialized] internal int m_EnabledActionsCount;
729722

730-
/// <summary>
731-
/// GUID converted from <see cref="m_Id"/>.
732-
/// </summary>
733-
[NonSerialized] private Guid m_Guid;
734-
735723
// Action maps that are created internally by singleton actions to hold their data
736724
// are never exposed and never serialized so there is no point allocating an m_Actions
737725
// array.
@@ -984,8 +972,7 @@ internal void ClearPerActionCachedBindingData()
984972

985973
internal void GenerateId()
986974
{
987-
m_Guid = Guid.NewGuid();
988-
m_Id = m_Guid.ToString();
975+
m_Id = Guid.NewGuid().ToString();
989976
}
990977

991978
/// <summary>
@@ -1218,12 +1205,12 @@ public InputBinding ToBinding()
12181205
};
12191206
}
12201207

1221-
public static BindingJson FromBinding(InputBinding binding)
1208+
public static BindingJson FromBinding(ref InputBinding binding)
12221209
{
12231210
return new BindingJson
12241211
{
12251212
name = binding.name,
1226-
id = binding.id.ToString(),
1213+
id = binding.m_Id,
12271214
path = binding.path,
12281215
action = binding.action,
12291216
interactions = binding.interactions,
@@ -1305,7 +1292,7 @@ public static WriteActionJson FromAction(InputAction action)
13051292
{
13061293
name = action.m_Name,
13071294
type = action.m_Type.ToString(),
1308-
id = action.id.ToString(),
1295+
id = action.m_Id,
13091296
expectedControlType = action.m_ExpectedControlType,
13101297
processors = action.processors,
13111298
interactions = action.interactions,
@@ -1352,7 +1339,7 @@ public static WriteMapJson FromMap(InputActionMap map)
13521339
jsonBindings = new BindingJson[bindingCount];
13531340

13541341
for (var i = 0; i < bindingCount; ++i)
1355-
jsonBindings[i] = BindingJson.FromBinding(bindings[i]);
1342+
jsonBindings[i] = BindingJson.FromBinding(ref bindings[i]);
13561343
}
13571344

13581345
return new WriteMapJson

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

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,12 @@ public Guid id
9898
{
9999
get
100100
{
101-
if (m_Guid == Guid.Empty && !string.IsNullOrEmpty(m_Id))
102-
m_Guid = new Guid(m_Id);
103-
return m_Guid;
104-
}
105-
set
106-
{
107-
m_Guid = value;
108-
m_Id = m_Guid.ToString();
101+
////REVIEW: this is inconsistent with InputActionMap and InputAction which generate IDs, if necessary
102+
if (string.IsNullOrEmpty(m_Id))
103+
return default;
104+
return new Guid(m_Id);
109105
}
106+
set => m_Id = value.ToString();
110107
}
111108

112109
/// <summary>
@@ -359,7 +356,6 @@ public InputBinding(string path, string action = null, string groups = null, str
359356
m_Processors = processors;
360357
m_Interactions = interactions;
361358
m_Name = name;
362-
m_Guid = default;
363359
m_Id = default;
364360
m_Flags = default;
365361
m_OverridePath = default;
@@ -376,21 +372,7 @@ public string GetNameOfComposite()
376372

377373
internal void GenerateId()
378374
{
379-
m_Guid = Guid.NewGuid();
380-
m_Id = m_Guid.ToString();
381-
}
382-
383-
internal string MakeSureIdIsInPlace()
384-
{
385-
if (m_Guid != Guid.Empty)
386-
return m_Id;
387-
388-
if (string.IsNullOrEmpty(m_Id))
389-
GenerateId();
390-
else
391-
m_Guid = new Guid(m_Id);
392-
393-
return m_Id;
375+
m_Id = Guid.NewGuid().ToString();
394376
}
395377

396378
public static InputBinding MaskByGroup(string group)
@@ -415,8 +397,6 @@ public static InputBinding MaskByGroups(params string[] groups)
415397
[NonSerialized] private string m_OverridePath;
416398
[NonSerialized] private string m_OverrideInteractions;
417399
[NonSerialized] private string m_OverrideProcessors;
418-
////REVIEW: do we actually need this or should we just convert from m_Id on the fly all the time?
419-
[NonSerialized] internal Guid m_Guid;
420400

421401
/// <summary>
422402
/// This is the bindings path which is effectively being used.

0 commit comments

Comments
 (0)