Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions Assets/Tests/InputSystem.Editor/CustomProcessorEnumTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,31 @@

yield return null;
}

[UnityTest]
public IEnumerator Migration_FromLegacyJson_ShouldConvertOrdinal_KeepInvertVector2_AndSeparators()
{
var legacyJson = m_Asset.ToJson().Replace("\"version\": 1", "\"version\": 0").Replace("Custom(SomeEnum=10)", "Custom(SomeEnum=1)");

// Add a trailing processor to verify the semicolon separator is preserved.
if (!legacyJson.Contains(";InvertVector2(invertX=true)"))
legacyJson = legacyJson.Replace("Custom(SomeEnum=1)\"", "Custom(SomeEnum=1);InvertVector2(invertX=true)\"");

var migratedAsset = InputActionAsset.FromJson(legacyJson);
Assume.That(migratedAsset, Is.Not.Null, "Failed to load legacy JSON into an InputActionAsset.");

var migratedJson = migratedAsset.ToJson();
Assume.That(migratedJson, Is.Not.Null.And.Not.Empty, "Migrated JSON was empty.");

Assert.Less(migratedJson.IndexOf("InvertVector2(invertX=true)", StringComparison.Ordinal), migratedJson.IndexOf(",Custom(SomeEnum=20)", StringComparison.Ordinal),
"Expected a comma between the first and second processors, with InvertVector2 first."
);

Assert.Greater(migratedJson.IndexOf(";InvertVector2(invertX=true)", StringComparison.Ordinal), migratedJson.IndexOf("Custom(SomeEnum=20)", StringComparison.Ordinal),

Check warning on line 131 in Assets/Tests/InputSystem.Editor/CustomProcessorEnumTest.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

Assets/Tests/InputSystem.Editor/CustomProcessorEnumTest.cs#L131

Added line #L131 was not covered by tests
"Expected a semicolon between the second and third processors, with the trailing InvertVector2 last."
);

yield return null;
}

Check warning on line 136 in Assets/Tests/InputSystem.Editor/CustomProcessorEnumTest.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

Assets/Tests/InputSystem.Editor/CustomProcessorEnumTest.cs#L135-L136

Added lines #L135 - L136 were not covered by tests
}
#endif
129 changes: 97 additions & 32 deletions Packages/com.unity.inputsystem/InputSystem/Actions/InputActionAsset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine.InputSystem.Editor;
using System.Globalization;
using UnityEngine.InputSystem.Utilities;

////TODO: make the FindAction logic available on any IEnumerable<InputAction> and IInputActionCollection via extension methods
Expand Down Expand Up @@ -1014,60 +1014,125 @@
{
if (parsedJson.version >= JsonVersion.Version1)
return;
if ((parsedJson.maps?.Length ?? 0) > 0 && (parsedJson.version) < JsonVersion.Version1)

if (parsedJson.maps == null || parsedJson.maps.Length == 0)
{
parsedJson.version = JsonVersion.Version1;
return;
}

for (var mi = 0; mi < parsedJson.maps.Length; ++mi)
{
for (var mi = 0; mi < parsedJson.maps.Length; ++mi)
var mapJson = parsedJson.maps[mi];
if (mapJson.actions == null || mapJson.actions.Length == 0)
continue;

Check warning on line 1028 in Packages/com.unity.inputsystem/InputSystem/Actions/InputActionAsset.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

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

Added line #L1028 was not covered by tests

for (var ai = 0; ai < mapJson.actions.Length; ++ai)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like 'ai' isn't really used so why not e.g foreach instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the iterator actionJson is immutable as the ReadActionJson is a struct so we use for loop instead of foreach.

{
var mapJson = parsedJson.maps[mi];
for (var ai = 0; ai < mapJson.actions.Length; ++ai)
var actionJson = mapJson.actions[ai];
var raw = actionJson.processors;
if (string.IsNullOrEmpty(raw))
continue;

var parts = System.Text.RegularExpressions.Regex.Split(raw, @"\s*([,;])\s*");
if (parts.Length == 0)
continue;

Check warning on line 1039 in Packages/com.unity.inputsystem/InputSystem/Actions/InputActionAsset.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

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

Added line #L1039 was not covered by tests

var tokens = new List<string>();
for (int i = 0; i < parts.Length; i += 2)
if (!string.IsNullOrEmpty(parts[i]))
tokens.Add(parts[i]);

if (tokens.Count == 0)
continue;

Check warning on line 1047 in Packages/com.unity.inputsystem/InputSystem/Actions/InputActionAsset.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

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

Added line #L1047 was not covered by tests

var parsed = new List<NameAndParameters>(tokens.Count);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this list may be moved to outer scope to avoid allocating on every iteration.

foreach (var t in tokens)
parsed.Add(NameAndParameters.Parse(t));

var rebuiltTokens = new List<string>(tokens.Count);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same with this list

var anyProcessorChanged = false;

for (int pi = 0; pi < parsed.Count; pi++)
{
var actionJson = mapJson.actions[ai];
var raw = actionJson.processors;
if (string.IsNullOrEmpty(raw))
var nap = parsed[pi];

var procType = InputSystem.TryGetProcessor(nap.name);
if (procType == null || nap.parameters.Count == 0)
{
rebuiltTokens.Add(tokens[pi]);

Check warning on line 1063 in Packages/com.unity.inputsystem/InputSystem/Actions/InputActionAsset.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

Packages/com.unity.inputsystem/InputSystem/Actions/InputActionAsset.cs#L1062-L1063

Added lines #L1062 - L1063 were not covered by tests
continue;
}

var dict = new Dictionary<string, string>(nap.parameters.Count, System.StringComparer.OrdinalIgnoreCase);
foreach (var p in nap.parameters)
dict[p.name] = p.value.ToString();

var changedThisProcessor = false;

var list = NameAndParameters.ParseMultiple(raw).ToList();
var rebuilt = new List<string>(list.Count);
foreach (var nap in list)
foreach (var field in procType.GetFields(BindingFlags.Public | BindingFlags.Instance))
{
var procType = InputSystem.TryGetProcessor(nap.name);
if (nap.parameters.Count == 0 || procType == null)
{
rebuilt.Add(nap.ToString());
if (!field.FieldType.IsEnum)
continue;

if (!dict.TryGetValue(field.Name, out var rawVal))
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))
if (int.TryParse(rawVal, NumberStyles.Integer, CultureInfo.InvariantCulture, out var n))
{
if (dict.TryGetValue(field.Name, out var ordS) && int.TryParse(ordS, out var ord))
var values = System.Enum.GetValues(field.FieldType);
var looksLikeOrdinal = n >= 0 && n < values.Length && !System.Enum.IsDefined(field.FieldType, n);
if (looksLikeOrdinal)
{
var values = Enum.GetValues(field.FieldType).Cast<object>().ToArray();
if (ord >= 0 && ord < values.Length)
var underlying = Convert.ToInt32(values.GetValue(n));
if (underlying != n)
{
dict[field.Name] = Convert.ToInt32(values[ord]).ToString();
anyChanged = true;
dict[field.Name] = underlying.ToString(CultureInfo.InvariantCulture);
changedThisProcessor = true;
}
}
}
}

if (!changedThisProcessor)
{
rebuiltTokens.Add(tokens[pi]);
}
else
{
var ordered = nap.parameters.Select(p =>
{
var v = dict.TryGetValue(p.name, out var nv) ? nv : p.value.ToString();
return $"{p.name}={v}";
});

var migrated = $"{nap.name}({string.Join(",", ordered)})";
rebuiltTokens.Add(migrated);
anyProcessorChanged = true;
}
}

if (!anyChanged)
if (anyProcessorChanged)
{
var sb = new System.Text.StringBuilder(raw.Length + 16);
int tokenIndex = 0;
for (int partIndex = 0; partIndex < parts.Length; ++partIndex)
{
if ((partIndex % 2) == 0)
{
rebuilt.Add(nap.ToString());
if (tokenIndex < rebuiltTokens.Count)
sb.Append(rebuiltTokens[tokenIndex++]);
}
else
{
var paramText = string.Join(",", dict.Select(kv => $"{kv.Key}={kv.Value}"));
rebuilt.Add($"{nap.name}({paramText})");
sb.Append(parts[partIndex]);
}
}

actionJson.processors = string.Join(";", rebuilt);
mapJson.actions[ai] = actionJson;
actionJson.processors = sb.ToString();
}
parsedJson.maps[mi] = mapJson;
mapJson.actions[ai] = actionJson;
}
parsedJson.maps[mi] = mapJson;
}
// Bump the version so we never re-migrate
parsedJson.version = JsonVersion.Version1;
Expand Down