-
Notifications
You must be signed in to change notification settings - Fork 329
FIX: ISXB-1674 - Input actions asset not converted correctly when upgrading from 1.14.1 (Regression) #2244
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
FIX: ISXB-1674 - Input actions asset not converted correctly when upgrading from 1.14.1 (Regression) #2244
Changes from all commits
a260324
00b37a5
d637adf
4e76813
a8ba533
ff05b1d
829c4aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1027,43 +1027,37 @@ internal void MigrateJson(ref ReadFileJson parsedJson) | |
continue; | ||
|
||
var list = NameAndParameters.ParseMultiple(raw).ToList(); | ||
var rebuilt = new List<string>(list.Count); | ||
var converted = new List<NameAndParameters>(list.Count); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still do not see why this isn't moved to outer scope and reused over iterations instead of being allocated on every iteration? Am I missing something? |
||
foreach (var nap in list) | ||
{ | ||
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)) | ||
var updatedParameters = new List<NamedValue>(nap.parameters.Count); | ||
foreach (var param in nap.parameters) | ||
{ | ||
if (dict.TryGetValue(field.Name, out var ordS) && int.TryParse(ordS, out var ord)) | ||
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<object>().ToArray(); | ||
if (ord >= 0 && ord < values.Length) | ||
var index = param.value.ToInt32(); | ||
var values = Enum.GetValues(fieldInfo.FieldType); | ||
if(index >= 0 && index < values.Length) | ||
{ | ||
dict[field.Name] = Convert.ToInt32(values[ord]).ToString(); | ||
anyChanged = true; | ||
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.SerializeMultiple(converted); | ||
mapJson.actions[ai] = actionJson; | ||
} | ||
parsedJson.maps[mi] = mapJson; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -83,11 +83,7 @@ private void OnParametersChanged(ParameterListView listView, int index) | |
|
||
private static string ToSerializableString(IEnumerable<NameAndParameters> parametersForEachListItem) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this function existing if it only forwards to NameAndParameters.SerializeMultiple? Seems redundant There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe keep the old name instead? |
||
{ | ||
if (parametersForEachListItem == null) | ||
return string.Empty; | ||
|
||
return string.Join(NamedValue.Separator, | ||
parametersForEachListItem.Select(x => x.ToString()).ToArray()); | ||
return NameAndParameters.SerializeMultiple(parametersForEachListItem); | ||
} | ||
|
||
public override void RedrawUI(InputActionsEditorState state) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,24 @@ | |
return $"{name}({parameterString})"; | ||
} | ||
|
||
internal static string SerializeMultiple(IEnumerable<NameAndParameters> list) | ||
{ | ||
if(list == null) | ||
ekcoh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return string.Empty; | ||
|
||
return string.Join(NamedValue.Separator, list.Select(x => x.ToString()).ToArray()); | ||
} | ||
|
||
internal static NameAndParameters Create(string name, IList<NamedValue> parameters) | ||
{ | ||
var result = new NameAndParameters | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: Why create a result variable here instead of just return new NameAndParameters { .... }; |
||
{ | ||
name = name, | ||
parameters = new ReadOnlyArray<NamedValue>(parameters.ToArray()) | ||
}; | ||
return result; | ||
} | ||
|
||
public static IEnumerable<NameAndParameters> ParseMultiple(string text) | ||
{ | ||
List<NameAndParameters> list = null; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is not verifying that the processor string can be parsed properly, which is what we want... we could still have a ";" separating them and those asserts would most likely pass.
@ekcoh do you know how we could get access to the parsed list of processors so we could assert on that instead? I could only find InputControl.processors or InputActionState.processors, but I don't know how to go from an InputActionAsset to InputControl or InputActionState
It seems InputAction.ResolveBindings end up calling InputBindingResolver.AddActionMap which then calls InstantiateWithParameters and that leads to the parsing and creation of processors, but I am not sure how we could call that from a test and then retrieve the list of processors to assert on that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, since the API design of Input Actions is based on string you can put whatever into Processors. So this test needs to complemented with code resolving the actions and the only way to actually prove the processors is correctly parsed and applied I believe would be to enable the actions and do ReadValue on the action after being resolved. To drive that action you also likely need a fake device to drive it with e.g. 1.0 and then see that e.g. scale processor multiples by 10 or similar.
In CoreTests_Actions.cs there are various examples on how to achieve this, e.g. search for tests containing ReadValue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would have been trivial if the string based API design was not a thing and instead actions had a list of actual processors on them.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @LeoUnity @ekcoh, our NameAndParameters.ParseMultiple() explicitly checks for both seperators
,
and;
between processors entries. Either way we get the same result however I agree with semantic issues from string only assertions.Should we go for
var parsed = NameAndParameters.ParseMultiple(action.processors).ToList(); Assert.That(parsed.Count, Is.EqualTo(expectedCount)); ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it just work to do this? Its simple and very little code
var legacyJson = ....
var modernJson = ....
var ported = InputActionAsset.FromJson(legacyJson).ToJson();
Assert.That(modernJson, Is.EqualTo(ported));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally you would have compared InputActionAsset objects but unfortunately it's not supported :/ Hence full cycle allows you to avoid the details. If porting is successful I would expect them both to be equal. Or even
var legacyJson = ....
var modernJson = ....
var ported = InputActionAsset.FromJson(legacyJson).ToJson();
Assert.That(InputActionAsset.FromJson(modernJson).ToJson(), Is.EqualTo(ported));
Since that wouldn't break either if formatting is updated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it should work, earlier the cause of regression was not considering
;
separator in the migration logic it only splits,
and keeps the;
unchanged caused the serialization to break. Since now as @LeoUnity suggested the robust approach to use the NameAndParameters the above FromJson and ported json assertion should do good I believe.