Skip to content

Commit 9506d23

Browse files
AswinRajGopalPauliusd01ekcohritamerkl
authored
FIX: Custom processors serialises enum by index rather than by value. (#2164)
Co-authored-by: Paulius Dervinis <54306142+Pauliusd01@users.noreply.github.com> Co-authored-by: Håkan Sidenvall <hakan.sidenvall@unity3d.com> Co-authored-by: Rita Merkl <127492464+ritamerkl@users.noreply.github.com>
1 parent 0f3413d commit 9506d23

File tree

9 files changed

+231
-9
lines changed

9 files changed

+231
-9
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#if UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS && UNITY_6000_0_OR_NEWER
2+
3+
using System;
4+
using NUnit.Framework;
5+
using System.Collections;
6+
using System.Linq;
7+
using UnityEditor;
8+
using UnityEngine;
9+
using UnityEngine.InputSystem;
10+
using UnityEngine.InputSystem.Editor;
11+
using UnityEngine.TestTools;
12+
using UnityEngine.UIElements;
13+
14+
internal enum SomeEnum
15+
{
16+
OptionA = 10,
17+
OptionB = 20
18+
}
19+
20+
#if UNITY_EDITOR
21+
[InitializeOnLoad]
22+
#endif
23+
internal class CustomProcessor : InputProcessor<float>
24+
{
25+
public SomeEnum SomeEnum;
26+
27+
#if UNITY_EDITOR
28+
static CustomProcessor()
29+
{
30+
Initialize();
31+
}
32+
33+
#endif
34+
35+
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
36+
private static void Initialize()
37+
{
38+
InputSystem.RegisterProcessor<CustomProcessor>();
39+
}
40+
41+
public override float Process(float value, InputControl control)
42+
{
43+
return value;
44+
}
45+
}
46+
47+
internal class CustomProcessorEnumTest : UIToolkitBaseTestWindow<InputActionsEditorWindow>
48+
{
49+
InputActionAsset m_Asset;
50+
51+
public override void OneTimeSetUp()
52+
{
53+
base.OneTimeSetUp();
54+
m_Asset = AssetDatabaseUtils.CreateAsset<InputActionAsset>();
55+
56+
var actionMap = m_Asset.AddActionMap("Action Map");
57+
58+
actionMap.AddAction("Action", InputActionType.Value, processors: "Custom(SomeEnum=10)");
59+
}
60+
61+
public override void OneTimeTearDown()
62+
{
63+
AssetDatabaseUtils.Restore();
64+
base.OneTimeTearDown();
65+
}
66+
67+
public override IEnumerator UnitySetup()
68+
{
69+
m_Window = InputActionsEditorWindow.OpenEditor(m_Asset);
70+
yield return null;
71+
}
72+
73+
[UnityTest]
74+
public IEnumerator ProcessorEnum_ShouldSerializeByValue_WhenSerializedToAsset()
75+
{
76+
// Serialize current asset to JSON, and check that initial JSON contains default enum value for OptionA
77+
var json = m_Window.currentAssetInEditor.ToJson();
78+
79+
Assert.That(json.Contains("Custom(SomeEnum=10)"), Is.True,
80+
"Serialized JSON does not contain the expected custom processor string for OptionA.");
81+
82+
// Query the dropdown with exactly two enum choices and check that the drop down is present in the UI
83+
var dropdownList = m_Window.rootVisualElement.Query<DropdownField>().Where(d => d.choices.Count == 2).ToList();
84+
Assume.That(dropdownList.Count > 0, Is.True, "Enum parameter dropdown not found in the UI.");
85+
86+
// Determine the new value to be set in the dropdown, focus the dropdown before dispatching the change
87+
var dropdown = dropdownList.First();
88+
var newValue = dropdown.choices[1];
89+
dropdown.Focus();
90+
dropdown.value = newValue;
91+
92+
// Create and send a change event from OptionA to OptionB
93+
var changeEvent = ChangeEvent<Enum>.GetPooled(SomeEnum.OptionA, SomeEnum.OptionB);
94+
changeEvent.target = dropdown;
95+
dropdown.SendEvent(changeEvent);
96+
97+
// Find the save button in the window, focus and click the save button to persist the changes
98+
var saveButton = m_Window.rootVisualElement.Q<Button>("save-asset-toolbar-button");
99+
Assume.That(saveButton, Is.Not.Null, "Save Asset button not found in the UI.");
100+
saveButton.Focus();
101+
SimulateClickOn(saveButton);
102+
103+
Assert.That(dropdown.value, Is.EqualTo(newValue));
104+
105+
// Verify that the updated JSON contains the new enum value for OpitonB
106+
var updatedJson = m_Window.currentAssetInEditor.ToJson();
107+
Assert.That(updatedJson.Contains("Custom(SomeEnum=20)"), Is.True, "Serialized JSON does not contain the updated custom processor string for OptionB.");
108+
109+
yield return null;
110+
}
111+
}
112+
#endif

Assets/Tests/InputSystem.Editor/CustomProcessorEnumTest.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/Tests/InputSystem/CoreTests_Actions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5215,8 +5215,8 @@ public void Actions_CanConvertAssetToAndFromJson()
52155215
static string MinimalJson(string name = null)
52165216
{
52175217
if (name != null)
5218-
return "{\n \"name\": \"" + name + "\",\n \"maps\": [],\n \"controlSchemes\": []\n}";
5219-
return "{\n \"maps\": [],\n \"controlSchemes\": []\n}";
5218+
return "{\n \"version\": 0,\n \"name\": \"" + name + "\",\n \"maps\": [],\n \"controlSchemes\": []\n}";
5219+
return "{\n \"version\": 0,\n \"maps\": [],\n \"controlSchemes\": []\n}";
52205220
}
52215221

52225222
[Test]

Packages/com.unity.inputsystem/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ however, it has to be formatted properly to pass verification tests.
3838
- Fixed the defaultActionMap dropdown in the PlayerInput component defaulting to <None> instead of the first ActionMap.
3939
- Fixed TrackedPoseDriver stops updating position and rotation when device is added after its initialization. [ISXB-1555](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1555)
4040
- Fixed PlayerInput component not working with C# Wrappers (ISXB-1535). This reverted changes done to fix [ISXB-920](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-920) but users can now fix it themselves.
41+
- Fixed an issue that caused input processors with enum properties to incorrectly serialise by index instead of by value [ISXB-1474](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1474)
4142

4243
## [1.14.0] - 2025-03-20
4344

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

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
using System;
22
using System.Collections;
33
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Reflection;
6+
using UnityEngine.InputSystem.Editor;
47
using UnityEngine.InputSystem.Utilities;
58

69
////TODO: make the FindAction logic available on any IEnumerable<InputAction> and IInputActionCollection via extension methods
@@ -275,6 +278,21 @@ public InputAction this[string actionNameOrId]
275278
return action;
276279
}
277280
}
281+
/// <summary>
282+
/// File‐format version constants for InputActionAsset JSON.
283+
/// </summary>
284+
static class JsonVersion
285+
{
286+
/// <summary>The original JSON version format for InputActionAsset.</summary>
287+
public const int Version0 = 0;
288+
289+
/// <summary>Updated JSON version format for InputActionAsset.</summary>
290+
/// <remarks>Changes representation of parameter values from being serialized by value to being serialized by value.</remarks>
291+
public const int Version1 = 1;
292+
293+
/// <summary>The current version.</summary>
294+
public const int Current = Version1;
295+
}
278296

279297
/// <summary>
280298
/// Return a JSON representation of the asset.
@@ -296,8 +314,10 @@ public InputAction this[string actionNameOrId]
296314
/// <seealso cref="FromJson"/>
297315
public string ToJson()
298316
{
317+
var hasContent = m_ActionMaps.LengthSafe() > 0 || m_ControlSchemes.LengthSafe() > 0;
299318
return JsonUtility.ToJson(new WriteFileJson
300319
{
320+
version = hasContent ? JsonVersion.Current : JsonVersion.Version0,
301321
name = name,
302322
maps = InputActionMap.WriteFileJson.FromMaps(m_ActionMaps).maps,
303323
controlSchemes = InputControlScheme.SchemeJson.ToJson(m_ControlSchemes),
@@ -379,6 +399,7 @@ public void LoadFromJson(string json)
379399
throw new ArgumentNullException(nameof(json));
380400

381401
var parsedJson = JsonUtility.FromJson<ReadFileJson>(json);
402+
MigrateJson(ref parsedJson);
382403
parsedJson.ToAsset(this);
383404
}
384405

@@ -950,6 +971,7 @@ private void OnDestroy()
950971
[Serializable]
951972
internal struct WriteFileJson
952973
{
974+
public int version;
953975
public string name;
954976
public InputActionMap.WriteMapJson[] maps;
955977
public InputControlScheme.SchemeJson[] controlSchemes;
@@ -965,6 +987,7 @@ internal struct WriteFileJsonNoName
965987
[Serializable]
966988
internal struct ReadFileJson
967989
{
990+
public int version;
968991
public string name;
969992
public InputActionMap.ReadMapJson[] maps;
970993
public InputControlScheme.SchemeJson[] controlSchemes;
@@ -981,5 +1004,73 @@ public void ToAsset(InputActionAsset asset)
9811004
map.m_Asset = asset;
9821005
}
9831006
}
1007+
1008+
/// <summary>
1009+
/// If parsedJson.version is older than Current, rewrite every
1010+
/// action.processors entry to replace “enumName(Ordinal=…)” with
1011+
/// “enumName(Value=…)” and bump parsedJson.version.
1012+
/// </summary>
1013+
internal void MigrateJson(ref ReadFileJson parsedJson)
1014+
{
1015+
if (parsedJson.version >= JsonVersion.Version1)
1016+
return;
1017+
if ((parsedJson.maps?.Length ?? 0) > 0 && (parsedJson.version) < JsonVersion.Version1)
1018+
{
1019+
for (var mi = 0; mi < parsedJson.maps.Length; ++mi)
1020+
{
1021+
var mapJson = parsedJson.maps[mi];
1022+
for (var ai = 0; ai < mapJson.actions.Length; ++ai)
1023+
{
1024+
var actionJson = mapJson.actions[ai];
1025+
var raw = actionJson.processors;
1026+
if (string.IsNullOrEmpty(raw))
1027+
continue;
1028+
1029+
var list = NameAndParameters.ParseMultiple(raw).ToList();
1030+
var rebuilt = new List<string>(list.Count);
1031+
foreach (var nap in list)
1032+
{
1033+
var procType = InputSystem.TryGetProcessor(nap.name);
1034+
if (nap.parameters.Count == 0 || procType == null)
1035+
{
1036+
rebuilt.Add(nap.ToString());
1037+
continue;
1038+
}
1039+
1040+
var dict = nap.parameters.ToDictionary(p => p.name, p => p.value.ToString());
1041+
var anyChanged = false;
1042+
foreach (var field in procType.GetFields(BindingFlags.Public | BindingFlags.Instance).Where(f => f.FieldType.IsEnum))
1043+
{
1044+
if (dict.TryGetValue(field.Name, out var ordS) && int.TryParse(ordS, out var ord))
1045+
{
1046+
var values = Enum.GetValues(field.FieldType).Cast<object>().ToArray();
1047+
if (ord >= 0 && ord < values.Length)
1048+
{
1049+
dict[field.Name] = Convert.ToInt32(values[ord]).ToString();
1050+
anyChanged = true;
1051+
}
1052+
}
1053+
}
1054+
1055+
if (!anyChanged)
1056+
{
1057+
rebuilt.Add(nap.ToString());
1058+
}
1059+
else
1060+
{
1061+
var paramText = string.Join(",", dict.Select(kv => $"{kv.Key}={kv.Value}"));
1062+
rebuilt.Add($"{nap.name}({paramText})");
1063+
}
1064+
}
1065+
1066+
actionJson.processors = string.Join(";", rebuilt);
1067+
mapJson.actions[ai] = actionJson;
1068+
}
1069+
parsedJson.maps[mi] = mapJson;
1070+
}
1071+
}
1072+
// Bump the version so we never re-migrate
1073+
parsedJson.version = JsonVersion.Version1;
1074+
}
9841075
}
9851076
}

Packages/com.unity.inputsystem/InputSystem/Editor/AssetEditor/ParameterListView.cs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -278,11 +278,26 @@ void OnEditEnd()
278278

279279
if (parameter.isEnum)
280280
{
281-
var intValue = parameter.value.value.ToInt32();
282-
var field = new DropdownField(label.text, parameter.enumNames.Select(x => x.text).ToList(), intValue);
283-
field.tooltip = label.tooltip;
284-
field.RegisterValueChangedCallback(evt => OnValueChanged(ref parameter, field.index, closedIndex));
285-
field.RegisterCallback<BlurEvent>(_ => OnEditEnd());
281+
var names = parameter.enumNames.Select(c => c.text).ToList();
282+
var rawValue = parameter.value.value.ToInt32();
283+
var selectedIndex = parameter.enumValues.IndexOf(rawValue);
284+
if (selectedIndex < 0 || selectedIndex >= names.Count)
285+
selectedIndex = 0;
286+
287+
var field = new DropdownField(label.text, names, selectedIndex)
288+
{
289+
tooltip = label.tooltip
290+
};
291+
292+
field.RegisterValueChangedCallback(evt =>
293+
{
294+
var newBackingValue = parameter.enumValues[field.index];
295+
parameter.value.value = PrimitiveValue.FromObject(newBackingValue).ConvertTo(parameter.value.type);
296+
m_Parameters[closedIndex] = parameter;
297+
onChange?.Invoke();
298+
});
299+
300+
field.RegisterCallback<BlurEvent>(_ => onChange?.Invoke());
286301
root.Add(field);
287302
}
288303
else if (parameter.value.type == TypeCode.Int64 || parameter.value.type == TypeCode.UInt64)

Packages/com.unity.inputsystem/InputSystem/Editor/AssetImporter/InputActionImporter.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ namespace UnityEngine.InputSystem.Editor
2828
[ScriptedImporter(kVersion, InputActionAsset.Extension)]
2929
internal class InputActionImporter : ScriptedImporter
3030
{
31-
private const int kVersion = 13;
31+
private const int kVersion = 14;
3232

3333
[SerializeField] private bool m_GenerateWrapperCode;
3434
[SerializeField] private string m_WrapperCodePath;
@@ -66,7 +66,6 @@ private static InputActionAsset CreateFromJson(AssetImportContext context)
6666
{
6767
// Attempt to parse JSON
6868
asset.LoadFromJson(content);
69-
7069
// Make sure action map names are unique within JSON file
7170
var names = new HashSet<string>();
7271
foreach (var map in asset.actionMaps)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class DefaultInputActions : IInputActionCollection2, IDisposable
4242
public @DefaultInputActions()
4343
{
4444
asset = InputActionAsset.FromJson(@"{
45+
""version"": 1,
4546
""name"": ""DefaultInputActions"",
4647
""maps"": [
4748
{

Packages/com.unity.inputsystem/InputSystem/Plugins/PlayerInput/DefaultInputActions.inputactions

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"version": 1,
23
"name": "DefaultInputActions",
34
"maps": [
45
{

0 commit comments

Comments
 (0)