Made ParseUtility helper to simplify and improve parsing of various input types

This commit is contained in:
Sinai
2021-05-09 01:25:26 +10:00
parent c828d9b642
commit c04a864b74
12 changed files with 512 additions and 106 deletions

View File

@ -171,7 +171,7 @@ namespace UnityExplorer.UI.CacheObject
#region Cache Member Util
public static bool CanProcessArgs(ParameterInfo[] parameters)
public static bool CanParseArgs(ParameterInfo[] parameters)
{
foreach (var param in parameters)
{
@ -180,7 +180,7 @@ namespace UnityExplorer.UI.CacheObject
if (pType.IsByRef && pType.HasElementType)
pType = pType.GetElementType();
if (pType != null && (pType.IsPrimitive || pType == typeof(string)))
if (pType != null && ParseUtility.CanParse(pType))
continue;
else
return false;
@ -260,7 +260,7 @@ namespace UnityExplorer.UI.CacheObject
return;
var args = mi.GetParameters();
if (!CanProcessArgs(args))
if (!CanParseArgs(args))
return;
sig += AppendArgsToSig(args);
@ -277,7 +277,7 @@ namespace UnityExplorer.UI.CacheObject
var pi = member as PropertyInfo;
var args = pi.GetIndexParameters();
if (!CanProcessArgs(args))
if (!CanParseArgs(args))
return;
if (!pi.CanRead && pi.CanWrite)

View File

@ -39,6 +39,9 @@ namespace UnityExplorer.UI.CacheObject
public Type FallbackType { get; protected set; }
public bool LastValueWasNull { get; private set; }
public ValueState State = ValueState.NotEvaluated;
public Type LastValueType;
public InteractiveValue IValue { get; private set; }
public Type CurrentIValueType { get; private set; }
public bool SubContentShowWanted { get; private set; }
@ -58,12 +61,6 @@ namespace UnityExplorer.UI.CacheObject
this.ValueLabelText = GetValueLabel();
}
// internals
// private static readonly Dictionary<string, MethodInfo> numberParseMethods = new Dictionary<string, MethodInfo>();
public ValueState State = ValueState.NotEvaluated;
protected const string NOT_YET_EVAL = "<color=grey>Not yet evaluated</color>";
public virtual void ReleasePooledObjects()
@ -127,6 +124,7 @@ namespace UnityExplorer.UI.CacheObject
if (HadException)
{
LastValueWasNull = true;
LastValueType = FallbackType;
State = ValueState.Exception;
}
else if (Value.IsNullOrDestroyed())
@ -158,6 +156,10 @@ namespace UnityExplorer.UI.CacheObject
public ValueState GetStateForType(Type type)
{
if (LastValueType == type)
return State;
LastValueType = type;
if (type == typeof(bool))
return ValueState.Boolean;
else if (type.IsPrimitive || type == typeof(decimal))
@ -184,17 +186,24 @@ namespace UnityExplorer.UI.CacheObject
switch (State)
{
case ValueState.NotEvaluated:
return $"<i>{NOT_YET_EVAL} ({SignatureHighlighter.Parse(FallbackType, true)})</i>";
case ValueState.Exception:
return $"<i><color=red>{LastException.ReflectionExToString()}</color></i>";
// bool and number dont want the label for the value at all
case ValueState.Boolean:
case ValueState.Number:
return null;
case ValueState.NotEvaluated:
return $"<i>{NOT_YET_EVAL} ({SignatureHighlighter.Parse(FallbackType, true)})</i>";
case ValueState.Exception:
return $"<i><color=red>{LastException.ReflectionExToString()}</color></i>";
// and valuestruct also doesnt want it if we can parse it
case ValueState.ValueStruct:
if (ParseUtility.CanParse(LastValueType))
return null;
break;
// string wants it trimmed to max 200 chars
case ValueState.String:
if (!LastValueWasNull)
{
@ -204,7 +213,8 @@ namespace UnityExplorer.UI.CacheObject
return $"\"{s}\"";
}
break;
// try to prefix the count of the collection for lists and dicts
case ValueState.Collection:
if (!LastValueWasNull)
{
@ -256,7 +266,6 @@ namespace UnityExplorer.UI.CacheObject
switch (State)
{
case ValueState.Exception:
//case ValueState.NullValue:
SetValueState(cell, ValueStateArgs.Default);
break;
case ValueState.Boolean:
@ -274,10 +283,15 @@ namespace UnityExplorer.UI.CacheObject
case ValueState.Enum:
SetValueState(cell, new ValueStateArgs(true, subContentButtonActive: CanWrite));
break;
case ValueState.Color:
case ValueState.ValueStruct:
if (ParseUtility.CanParse(LastValueType))
SetValueState(cell, new ValueStateArgs(false, false, null, true, false, true, CanWrite, true, true));
else
SetValueState(cell, new ValueStateArgs(true, inspectActive: true, subContentButtonActive: true));
break;
case ValueState.Collection:
case ValueState.Dictionary:
case ValueState.ValueStruct:
case ValueState.Color:
SetValueState(cell, new ValueStateArgs(true, inspectActive: !LastValueWasNull, subContentButtonActive: !LastValueWasNull));
break;
case ValueState.Unsupported:
@ -303,7 +317,7 @@ namespace UnityExplorer.UI.CacheObject
// Type label (for primitives)
cell.TypeLabel.gameObject.SetActive(args.typeLabelActive);
if (args.typeLabelActive)
cell.TypeLabel.text = SignatureHighlighter.Parse(Value.GetActualType(), false);
cell.TypeLabel.text = SignatureHighlighter.Parse(LastValueType, false);
// toggle for bools
cell.Toggle.gameObject.SetActive(args.toggleActive);
@ -318,7 +332,7 @@ namespace UnityExplorer.UI.CacheObject
cell.InputField.UIRoot.SetActive(args.inputActive);
if (args.inputActive)
{
cell.InputField.Text = Value.ToString();
cell.InputField.Text = ParseUtility.ToStringForInput(Value, LastValueType);
cell.InputField.InputField.readOnly = !CanWrite;
}
@ -342,21 +356,15 @@ namespace UnityExplorer.UI.CacheObject
SetUserValue(this.CellView.Toggle.isOn);
else
{
try
if (ParseUtility.TryParse(CellView.InputField.Text, LastValueType, out object value, out Exception ex))
{
var type = Value.GetType();
var val = ReflectionUtility.GetMethodInfo(type, "Parse", ArgumentUtility.ParseArgs)
.Invoke(null, new object[] { CellView.InputField.Text });
SetUserValue(val);
SetUserValue(value);
}
catch (ArgumentException) { } // ignore bad user input
catch (FormatException) { }
catch (OverflowException) { }
catch (Exception ex)
else
{
ExplorerCore.LogWarning("CacheObjectBase OnCellApplyClicked (number): " + ex.ToString());
ExplorerCore.LogWarning("Unable to parse input!");
if (ex != null)
ExplorerCore.Log(ex.ReflectionExToString());
}
}

View File

@ -155,16 +155,18 @@ namespace UnityExplorer.UI.CacheObject.Views
InputField = UIFactory.CreateInputField(rightHoriGroup, "InputField", "...");
UIFactory.SetLayoutElement(InputField.UIRoot, minWidth: 150, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0);
// Inspect and apply buttons
// Apply
InspectButton = UIFactory.CreateButton(rightHoriGroup, "InspectButton", "Inspect", new Color(0.15f, 0.15f, 0.15f));
UIFactory.SetLayoutElement(InspectButton.Button.gameObject, minWidth: 60, flexibleWidth: 0, minHeight: 25);
InspectButton.OnClick += InspectClicked;
ApplyButton = UIFactory.CreateButton(rightHoriGroup, "ApplyButton", "Apply", new Color(0.15f, 0.15f, 0.15f));
ApplyButton = UIFactory.CreateButton(rightHoriGroup, "ApplyButton", "Apply", new Color(0.15f, 0.19f, 0.15f));
UIFactory.SetLayoutElement(ApplyButton.Button.gameObject, minWidth: 70, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0);
ApplyButton.OnClick += ApplyClicked;
// Inspect
InspectButton = UIFactory.CreateButton(rightHoriGroup, "InspectButton", "Inspect", new Color(0.15f, 0.15f, 0.15f));
UIFactory.SetLayoutElement(InspectButton.Button.gameObject, minWidth: 70, flexibleWidth: 0, minHeight: 25);
InspectButton.OnClick += InspectClicked;
// Main value label
ValueLabel = UIFactory.CreateLabel(rightHoriGroup, "ValueLabel", "Value goes here", TextAnchor.MiddleLeft);

View File

@ -33,7 +33,7 @@ namespace UnityExplorer.UI.CacheObject.Views
private readonly List<Text> genericArgLabels = new List<Text>();
private readonly List<TypeCompleter> genericAutocompleters = new List<TypeCompleter>();
private readonly List<InputFieldRef> inputFieldCache = new List<InputFieldRef>();
private readonly List<InputFieldRef> inputFields = new List<InputFieldRef>();
public void OnBorrowedFromPool(CacheMember owner)
{
@ -52,7 +52,7 @@ namespace UnityExplorer.UI.CacheObject.Views
public void OnReturnToPool()
{
foreach (var input in inputFieldCache)
foreach (var input in inputFields)
input.Text = "";
this.Owner = null;
@ -99,15 +99,11 @@ namespace UnityExplorer.UI.CacheObject.Views
continue;
}
try
if (!ParseUtility.TryParse(input, type, out outArgs[i], out Exception ex))
{
var parse = ReflectionUtility.GetMethodInfo(type, "Parse", ArgumentUtility.ParseArgs);
outArgs[i] = parse.Invoke(null, new object[] { input });
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Cannot parse argument '{arg.Name}' ({arg.ParameterType.Name}), {ex.GetType().Name}: {ex.Message}");
outArgs[i] = null;
ExplorerCore.LogWarning($"Cannot parse argument '{arg.Name}' ({arg.ParameterType.Name})" +
$"{(ex == null ? "" : $", {ex.GetType().Name}: {ex.Message}")}");
}
}
@ -199,6 +195,15 @@ namespace UnityExplorer.UI.CacheObject.Views
argRows[i].SetActive(true);
argLabels[i].text = $"{SignatureHighlighter.Parse(arg.ParameterType, false)} <color={SignatureHighlighter.LOCAL_ARG}>{arg.Name}</color>";
if (arg.ParameterType == typeof(string))
inputFields[i].PlaceholderText.text = "";
else
{
var elemType = arg.ParameterType;
if (elemType.IsByRef)
elemType = elemType.GetElementType();
inputFields[i].PlaceholderText.text = $"eg. {ParseUtility.GetExampleInput(elemType)}";
}
}
}
@ -228,7 +233,7 @@ namespace UnityExplorer.UI.CacheObject.Views
inputField.InputField.lineType = InputField.LineType.MultiLineNewline;
inputField.UIRoot.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
inputField.OnValueChanged += (string val) => { inputArray[index] = val; };
inputFieldCache.Add(inputField);
inputFields.Add(inputField);
if (autocomplete)
genericAutocompleters.Add(new TypeCompleter(null, inputField));

View File

@ -26,33 +26,31 @@ namespace UnityExplorer.UI.IValues
Fields = fields;
}
public void SetValue(object instance, string value, int fieldIndex)
public void SetValue(object instance, string input, int fieldIndex)
{
try
{
var field = Fields[fieldIndex];
var field = Fields[fieldIndex];
object val;
if (field.FieldType == typeof(string))
val = value;
else
val = ReflectionUtility.GetMethodInfo(field.FieldType, "Parse", ArgumentUtility.ParseArgs)
.Invoke(null, new object[] { value });
field.SetValue(instance, val);
}
catch (FormatException) { ExplorerCore.LogWarning($"Invalid argument '{value}'!"); }
catch (ArgumentException) { ExplorerCore.LogWarning($"Invalid argument '{value}'!"); }
catch (OverflowException) { ExplorerCore.LogWarning($"Invalid argument '{value}'!"); }
catch (Exception ex)
object val;
if (field.FieldType == typeof(string))
val = input;
else
{
ExplorerCore.Log("Excepting setting value '" + value + "'! " + ex);
if (!ParseUtility.TryParse(input, field.FieldType, out val, out Exception ex))
{
ExplorerCore.LogWarning("Unable to parse input!");
if (ex != null) ExplorerCore.Log(ex.ReflectionExToString());
return;
}
}
field.SetValue(instance, val);
}
public string GetValue(object instance, int fieldIndex)
{
return Fields[fieldIndex].GetValue(instance)?.ToString() ?? "";
var field = Fields[fieldIndex];
var value = field.GetValue(instance);
return ParseUtility.ToStringForInput(value, field.FieldType);
}
}
@ -69,19 +67,21 @@ namespace UnityExplorer.UI.IValues
if (typeSupportCache.TryGetValue(type.AssemblyQualifiedName, out var info))
return info.IsSupported;
var supported = true;
var supported = false;
var fields = type.GetFields(INSTANCE_FLAGS);
if (fields.Any(it => !it.FieldType.IsPrimitive && it.FieldType != typeof(string)))
if (fields.Length > 0)
{
supported = false;
info = new StructInfo(supported, null);
}
else
{
supported = true;
info = new StructInfo(supported, fields);
if (fields.Any(it => !ParseUtility.CanParse(it.FieldType)))
{
supported = false;
info = new StructInfo(supported, null);
}
else
{
supported = true;
info = new StructInfo(supported, fields);
}
}
typeSupportCache.Add(type.AssemblyQualifiedName, info);
@ -182,12 +182,12 @@ namespace UnityExplorer.UI.IValues
fieldRows.Add(row);
var label = UIFactory.CreateLabel(row, "Label", "notset", TextAnchor.MiddleRight);
UIFactory.SetLayoutElement(label.gameObject, minHeight: 25, minWidth: 150, flexibleWidth: 0);
UIFactory.SetLayoutElement(label.gameObject, minHeight: 25, minWidth: 175, flexibleWidth: 0);
label.horizontalOverflow = HorizontalWrapMode.Wrap;
labels.Add(label);
var input = UIFactory.CreateInputField(row, "InputField", "...");
UIFactory.SetLayoutElement(input.UIRoot, minHeight: 25, minWidth: 100);
UIFactory.SetLayoutElement(input.UIRoot, minHeight: 25, minWidth: 200);
var fitter = input.UIRoot.AddComponent<ContentSizeFitter>();
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
fitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
@ -204,7 +204,7 @@ namespace UnityExplorer.UI.IValues
UIFactory.SetLayoutElement(UIRoot, minHeight: 25, flexibleWidth: 9999);
applyButton = UIFactory.CreateButton(UIRoot, "ApplyButton", "Apply", new Color(0.2f, 0.27f, 0.2f));
UIFactory.SetLayoutElement(applyButton.Button.gameObject, minHeight: 25, minWidth: 100);
UIFactory.SetLayoutElement(applyButton.Button.gameObject, minHeight: 25, minWidth: 175);
applyButton.OnClick += OnApplyClicked;
return UIRoot;

View File

@ -295,14 +295,12 @@ namespace UnityExplorer.UI.Panels
{
// Window Anchors helpers
internal static CultureInfo _enCulture = new CultureInfo("en-US");
internal static string RectAnchorsToString(this RectTransform rect)
{
if (!rect)
throw new ArgumentNullException("rect");
return string.Format(_enCulture, "{0},{1},{2},{3}", new object[]
return string.Format(ParseUtility.en_US, "{0},{1},{2},{3}", new object[]
{
rect.anchorMin.x,
rect.anchorMin.y,
@ -322,10 +320,10 @@ namespace UnityExplorer.UI.Panels
throw new Exception($"stringAnchors split is unexpected length: {split.Length}");
Vector4 anchors;
anchors.x = float.Parse(split[0], _enCulture);
anchors.y = float.Parse(split[1], _enCulture);
anchors.z = float.Parse(split[2], _enCulture);
anchors.w = float.Parse(split[3], _enCulture);
anchors.x = float.Parse(split[0], ParseUtility.en_US);
anchors.y = float.Parse(split[1], ParseUtility.en_US);
anchors.z = float.Parse(split[2], ParseUtility.en_US);
anchors.w = float.Parse(split[3], ParseUtility.en_US);
panel.anchorMin = new Vector2(anchors.x, anchors.y);
panel.anchorMax = new Vector2(anchors.z, anchors.w);
@ -336,7 +334,7 @@ namespace UnityExplorer.UI.Panels
if (!rect)
throw new ArgumentNullException("rect");
return string.Format(_enCulture, "{0},{1}", new object[]
return string.Format(ParseUtility.en_US, "{0},{1}", new object[]
{
rect.localPosition.x, rect.localPosition.y
});
@ -350,8 +348,8 @@ namespace UnityExplorer.UI.Panels
throw new Exception($"stringPosition split is unexpected length: {split.Length}");
Vector3 vector = rect.localPosition;
vector.x = float.Parse(split[0], _enCulture);
vector.y = float.Parse(split[1], _enCulture);
vector.x = float.Parse(split[0], ParseUtility.en_US);
vector.y = float.Parse(split[1], ParseUtility.en_US);
rect.localPosition = vector;
}
}