diff --git a/Assets/Tests/InputSystem.Editor/CustomProcessorEnumTest.cs b/Assets/Tests/InputSystem.Editor/CustomProcessorEnumTest.cs index f2e9cb174a..08865b9417 100644 --- a/Assets/Tests/InputSystem.Editor/CustomProcessorEnumTest.cs +++ b/Assets/Tests/InputSystem.Editor/CustomProcessorEnumTest.cs @@ -8,6 +8,7 @@ using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Editor; +using UnityEngine.InputSystem.Processors; using UnityEngine.TestTools; using UnityEngine.UIElements; @@ -108,5 +109,69 @@ public IEnumerator ProcessorEnum_ShouldSerializeByValue_WhenSerializedToAsset() yield return null; } + + [Test] + [Description("Regression test for case ISXB-1674")] + public void Migration_ShouldProduceValidActionAsset_WithEnumProcessorConverted() + { + const string k_Json = @" + { + ""name"": ""InputSystem_Actions"", + ""maps"": [ + { + ""name"": ""Player"", + ""id"": ""df70fa95-8a34-4494-b137-73ab6b9c7d37"", + ""actions"": [ + { + ""name"": ""Move"", + ""type"": ""Value"", + ""id"": ""938a78a0-f8c6-4b2e-b8a7-d3c26d83a4e9"", + ""expectedControlType"": ""Vector2"", + ""processors"": ""StickDeadzone,InvertVector2(invertX=false),Custom(SomeEnum=1)"", + ""interactions"": """", + ""initialStateCheck"": true + } + ], + ""bindings"": [ + { + ""name"": """", + ""id"": ""c9a175a0-a5ed-4e2c-b3a9-1d4d3d3a7a9a"", + ""path"": ""/leftStick"", + ""interactions"": """", + ""processors"": """", + ""groups"": """", + ""action"": ""Move"", + ""isComposite"": false, + ""isPartOfComposite"": false + } + ] + } + ], + ""controlSchemes"": [] + }"; + + var asset = InputActionAsset.FromJson(k_Json); + + // Enable the action to call rebinding + asset.FindAction("Move").Enable(); + + var map = asset.FindActionMap("Player"); + + // Directly tests the outcome of the migration and parsing logic, which is the core goal. + var instantiatedProcessors = map.m_State.processors; + Assert.That(map.m_State.totalProcessorCount, Is.EqualTo(3), "Should create exactly three processors."); + + // Verify the order and type of each processor. + Assert.That(instantiatedProcessors[0], Is.TypeOf(), "First processor should be StickDeadzone."); + Assert.That(instantiatedProcessors[1], Is.TypeOf(), "Second processor should be InvertVector2."); + Assert.That(instantiatedProcessors[2], Is.TypeOf(), "Third processor should be the custom type."); + + // Verify the specific data for processors with parameters. + var invertProcessor = (InvertVector2Processor)instantiatedProcessors[1]; + Assert.That(invertProcessor.invertX, Is.False, "invertX parameter should be false."); + + var customProcessor = (CustomProcessor)instantiatedProcessors[2]; + Assert.That(customProcessor.SomeEnum, Is.EqualTo(SomeEnum.OptionB), "Enum parameter should be parsed correctly to OptionB."); + } } #endif diff --git a/Packages/com.unity.inputsystem/CHANGELOG.md b/Packages/com.unity.inputsystem/CHANGELOG.md index 8b16ee6937..992daa34ac 100644 --- a/Packages/com.unity.inputsystem/CHANGELOG.md +++ b/Packages/com.unity.inputsystem/CHANGELOG.md @@ -35,6 +35,7 @@ however, it has to be formatted properly to pass verification tests. - Fix usage of correct data format for stick axes in HID Layout Builder ([User contribution](https://github.com/Unity-Technologies/InputSystem/pull/2245)) - Fixed InputSystemUIInputModule calling pointer events on parent objects even when the "Send Pointer Hover To Parent" is off on 2022.3.XX. This was was previously only available on Unity 6 versions since 1.11.0. [ISXB-1296] - Fixed an issue where the action icon would shrink or disappear from UI when an action has a very long name. [ISXB-1650](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1650). +- Fixed upgrading input actions containing multiple processors [ISXB-1674](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1674). ## [1.15.0] - 2025-10-03 diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionAsset.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionAsset.cs index 3b9a70fcca..e655a6a712 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionAsset.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionAsset.cs @@ -1016,6 +1016,11 @@ internal void MigrateJson(ref ReadFileJson parsedJson) return; if ((parsedJson.maps?.Length ?? 0) > 0 && (parsedJson.version) < JsonVersion.Version1) { + List parsedList = null; + var converted = new List(8); + var updatedParameters = new List(4); + var enumValuesCache = new Dictionary(8); + for (var mi = 0; mi < parsedJson.maps.Length; ++mi) { var mapJson = parsedJson.maps[mi]; @@ -1026,44 +1031,49 @@ internal void MigrateJson(ref ReadFileJson parsedJson) if (string.IsNullOrEmpty(raw)) continue; - var list = NameAndParameters.ParseMultiple(raw).ToList(); - var rebuilt = new List(list.Count); - foreach (var nap in list) + if (!NameAndParameters.ParseMultiple(raw, ref parsedList)) + continue; + + converted.Clear(); + + for (int i = 0; i < parsedList.Count; ++i) { + var nap = parsedList[i]; var procType = InputSystem.TryGetProcessor(nap.name); if (nap.parameters.Count == 0 || procType == null) { - rebuilt.Add(nap.ToString()); + converted.Add(nap); continue; } - - var dict = nap.parameters.ToDictionary(p => p.name, p => p.value.ToString()); - var anyChanged = false; - foreach (var field in procType.GetFields(BindingFlags.Public | BindingFlags.Instance).Where(f => f.FieldType.IsEnum)) + updatedParameters.Clear(); + for (int k = 0; k < nap.parameters.Count; ++k) { - if (dict.TryGetValue(field.Name, out var ordS) && int.TryParse(ordS, out var ord)) + var param = nap.parameters[k]; + var updatedPar = param; + + var fieldInfo = procType.GetField(param.name, BindingFlags.Public | BindingFlags.Instance); + if (fieldInfo != null && fieldInfo.FieldType.IsEnum) { - var values = Enum.GetValues(field.FieldType).Cast().ToArray(); - if (ord >= 0 && ord < values.Length) + var index = param.value.ToInt32(); + if (index >= 0) { - dict[field.Name] = Convert.ToInt32(values[ord]).ToString(); - anyChanged = true; + if (!enumValuesCache.TryGetValue(fieldInfo.FieldType, out var values)) + { + values = Enum.GetValues(fieldInfo.FieldType); + enumValuesCache[fieldInfo.FieldType] = values; + } + if (index < values.Length) + { + var convertedValue = Convert.ToInt32(values.GetValue(index)); + updatedPar = NamedValue.From(param.name, convertedValue); + } } } + updatedParameters.Add(updatedPar); } - - if (!anyChanged) - { - rebuilt.Add(nap.ToString()); - } - else - { - var paramText = string.Join(",", dict.Select(kv => $"{kv.Key}={kv.Value}")); - rebuilt.Add($"{nap.name}({paramText})"); - } + converted.Add(NameAndParameters.Create(nap.name, updatedParameters)); } - - actionJson.processors = string.Join(";", rebuilt); + actionJson.processors = NameAndParameters.ToSerializableString(converted); mapJson.actions[ai] = actionJson; } parsedJson.maps[mi] = mapJson; diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/NameAndParametersListView.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/NameAndParametersListView.cs index fd51f4d26e..6c253c46b4 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/NameAndParametersListView.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/NameAndParametersListView.cs @@ -37,7 +37,7 @@ public void OnAddElement(string name) var newElement = new NameAndParameters() { name = name}; interactionsOrProcessorsList.Add(newElement); - m_ListProperty.stringValue = ToSerializableString(interactionsOrProcessorsList); + m_ListProperty.stringValue = NameAndParameters.ToSerializableString(interactionsOrProcessorsList); m_ListProperty.serializedObject.ApplyModifiedProperties(); } @@ -61,7 +61,7 @@ private void SwapElement(int oldIndex, int newIndex) if (interactionsOrProcessors.Length == 0 || !newIndexIsValid || !oldIndexIsValid) return; MemoryHelpers.Swap(ref interactionsOrProcessors[oldIndex], ref interactionsOrProcessors[newIndex]); - m_ListProperty.stringValue = ToSerializableString(interactionsOrProcessors); + m_ListProperty.stringValue = NameAndParameters.ToSerializableString(interactionsOrProcessors); m_ListProperty.serializedObject.ApplyModifiedProperties(); } @@ -69,7 +69,7 @@ private void DeleteElement(int index) { var interactionsOrProcessorsList = NameAndParameters.ParseMultiple(m_ListProperty.stringValue).ToList(); interactionsOrProcessorsList.RemoveAt(index); - m_ListProperty.stringValue = ToSerializableString(interactionsOrProcessorsList); + m_ListProperty.stringValue = NameAndParameters.ToSerializableString(interactionsOrProcessorsList); m_ListProperty.serializedObject.ApplyModifiedProperties(); } @@ -77,19 +77,10 @@ private void OnParametersChanged(ParameterListView listView, int index) { var interactionsOrProcessorsList = NameAndParameters.ParseMultiple(m_ListProperty.stringValue).ToList(); interactionsOrProcessorsList[index] = new NameAndParameters { name = interactionsOrProcessorsList[index].name, parameters = listView.GetParameters() }; - m_ListProperty.stringValue = ToSerializableString(interactionsOrProcessorsList); + m_ListProperty.stringValue = NameAndParameters.ToSerializableString(interactionsOrProcessorsList); m_ListProperty.serializedObject.ApplyModifiedProperties(); } - private static string ToSerializableString(IEnumerable parametersForEachListItem) - { - if (parametersForEachListItem == null) - return string.Empty; - - return string.Join(NamedValue.Separator, - parametersForEachListItem.Select(x => x.ToString()).ToArray()); - } - public override void RedrawUI(InputActionsEditorState state) { if (m_ContentContainer != null) diff --git a/Packages/com.unity.inputsystem/InputSystem/Utilities/NameAndParameters.cs b/Packages/com.unity.inputsystem/InputSystem/Utilities/NameAndParameters.cs index 15ceed089c..b4491e8eec 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Utilities/NameAndParameters.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Utilities/NameAndParameters.cs @@ -27,6 +27,23 @@ public override string ToString() return $"{name}({parameterString})"; } + internal static string ToSerializableString(IEnumerable list) + { + if (list == null) + return string.Empty; + + return string.Join(NamedValue.Separator, list.Select(x => x.ToString()).ToArray()); + } + + internal static NameAndParameters Create(string name, IList parameters) + { + return new NameAndParameters + { + name = name, + parameters = new ReadOnlyArray(parameters.ToArray()) + }; + } + public static IEnumerable ParseMultiple(string text) { List list = null;