Compare commits

..

6 Commits
4.6.2 ... 4.6.4

Author SHA1 Message Date
d6a07f05ea Bump version 2022-04-10 05:37:28 +10:00
27d5398dea Add throttling to prune method, remove logging 2022-04-10 05:32:07 +10:00
7a5570a070 Bump version 2022-04-09 18:59:34 +10:00
d40537775f Bump UniverseLib 2022-04-09 18:59:13 +10:00
9b6f3fd3ea Cleanups / refactoring 2022-04-09 18:58:56 +10:00
bacac929e9 Show InputField for exceptions to view/copy full exception 2022-04-01 18:14:50 +11:00
15 changed files with 102 additions and 104 deletions

View File

@ -72,13 +72,11 @@ namespace UnityExplorer.CacheObject
else
ret = Activator.CreateInstance(returnType, ArgumentUtility.EmptyArgs);
HadException = false;
LastException = null;
return ret;
}
catch (Exception ex)
{
HadException = true;
LastException = ex;
return null;
}

View File

@ -32,13 +32,11 @@ namespace UnityExplorer.CacheObject
try
{
var ret = FieldInfo.GetValue(DeclaringInstance);
HadException = false;
LastException = null;
return ret;
}
catch (Exception ex)
{
HadException = true;
LastException = ex;
return null;
}

View File

@ -45,13 +45,11 @@ namespace UnityExplorer.CacheObject
ret = methodInfo.Invoke(DeclaringInstance, Evaluator.TryParseArguments());
else
ret = methodInfo.Invoke(DeclaringInstance, ArgumentUtility.EmptyArgs);
HadException = false;
LastException = null;
return ret;
}
catch (Exception ex)
{
HadException = true;
LastException = ex;
return null;
}

View File

@ -57,7 +57,6 @@ namespace UnityExplorer.CacheObject
public abstract bool ShouldAutoEvaluate { get; }
public abstract bool HasArguments { get; }
public abstract bool CanWrite { get; }
public bool HadException { get; protected set; }
public Exception LastException { get; protected set; }
public virtual void SetFallbackType(Type fallbackType)
@ -106,8 +105,8 @@ namespace UnityExplorer.CacheObject
if (CellView != null)
SetDataToCell(CellView);
// If the owner's parent CacheObject is set, we are setting the value of an inspected struct.
// Set the inspector target as the value back to that parent cacheobject.
// If the owner's ParentCacheObject is set, we are setting the value of an inspected struct.
// Set the inspector target as the value back to that parent.
if (Owner.ParentCacheObject != null)
Owner.ParentCacheObject.SetUserValue(Owner.Target);
}
@ -137,7 +136,7 @@ namespace UnityExplorer.CacheObject
{
var prevState = State;
if (HadException)
if (LastException != null)
{
LastValueWasNull = true;
LastValueType = FallbackType;
@ -158,7 +157,7 @@ namespace UnityExplorer.CacheObject
{
// If we changed states (always needs IValue change)
// or if the value is null, and the fallback type isnt string (we always want to edit strings).
if (State != prevState || (State != ValueState.String && Value.IsNullOrDestroyed()))
if (State != prevState || (State != ValueState.String && State != ValueState.Exception && Value.IsNullOrDestroyed()))
{
// need to return IValue
ReleaseIValue();
@ -206,7 +205,7 @@ namespace UnityExplorer.CacheObject
return $"<i>{NOT_YET_EVAL} ({SignatureHighlighter.Parse(FallbackType, true)})</i>";
case ValueState.Exception:
return $"<i><color=red>{LastException.ReflectionExToString()}</color></i>";
return $"<i><color=#eb4034>{LastException.ReflectionExToString()}</color></i>";
// bool and number dont want the label for the value at all
case ValueState.Boolean:
@ -291,36 +290,36 @@ namespace UnityExplorer.CacheObject
switch (State)
{
case ValueState.Exception:
SetValueState(cell, ValueStateArgs.Default);
SetValueState(cell, new(true, subContentButtonActive: true));
break;
case ValueState.Boolean:
SetValueState(cell, new ValueStateArgs(false, toggleActive: true, applyActive: CanWrite));
SetValueState(cell, new(false, toggleActive: true, applyActive: CanWrite));
break;
case ValueState.Number:
SetValueState(cell, new ValueStateArgs(false, typeLabelActive: true, inputActive: true, applyActive: CanWrite));
SetValueState(cell, new(false, typeLabelActive: true, inputActive: true, applyActive: CanWrite));
break;
case ValueState.String:
if (LastValueWasNull)
SetValueState(cell, new ValueStateArgs(true, subContentButtonActive: true));
SetValueState(cell, new(true, subContentButtonActive: true));
else
SetValueState(cell, new ValueStateArgs(true, false, SignatureHighlighter.StringOrange, subContentButtonActive: true));
SetValueState(cell, new(true, false, SignatureHighlighter.StringOrange, subContentButtonActive: true));
break;
case ValueState.Enum:
SetValueState(cell, new ValueStateArgs(true, subContentButtonActive: CanWrite));
SetValueState(cell, new(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));
SetValueState(cell, new(false, false, null, true, false, true, CanWrite, true, true));
else
SetValueState(cell, new ValueStateArgs(true, inspectActive: true, subContentButtonActive: true));
SetValueState(cell, new(true, inspectActive: true, subContentButtonActive: true));
break;
case ValueState.Collection:
case ValueState.Dictionary:
SetValueState(cell, new ValueStateArgs(true, inspectActive: !LastValueWasNull, subContentButtonActive: !LastValueWasNull));
SetValueState(cell, new(true, inspectActive: !LastValueWasNull, subContentButtonActive: !LastValueWasNull));
break;
case ValueState.Unsupported:
SetValueState(cell, new ValueStateArgs(true, inspectActive: !LastValueWasNull));
SetValueState(cell, new (true, inspectActive: !LastValueWasNull));
break;
}
@ -368,8 +367,10 @@ namespace UnityExplorer.CacheObject
if (cell.InspectButton != null)
cell.InspectButton.Component.gameObject.SetActive(args.inspectActive && !LastValueWasNull);
// allow IValue for null strings though
cell.SubContentButton.Component.gameObject.SetActive(args.subContentButtonActive && (!LastValueWasNull || State == ValueState.String));
// set subcontent button if needed, and for null strings and exceptions
cell.SubContentButton.Component.gameObject.SetActive(
args.subContentButtonActive
&& (!LastValueWasNull || State == ValueState.String || State == ValueState.Exception));
}
// CacheObjectCell Apply
@ -401,7 +402,7 @@ namespace UnityExplorer.CacheObject
{
if (this.IValue == null)
{
var ivalueType = InteractiveValue.GetIValueTypeForState(State);
Type ivalueType = InteractiveValue.GetIValueTypeForState(State);
if (ivalueType == null)
return;
@ -455,6 +456,7 @@ namespace UnityExplorer.CacheObject
{
inactiveIValueHolder = new GameObject("Temp_IValue_Holder");
GameObject.DontDestroyOnLoad(inactiveIValueHolder);
inactiveIValueHolder.hideFlags = HideFlags.HideAndDontSave;
inactiveIValueHolder.transform.parent = UniversalUI.PoolHolder.transform;
inactiveIValueHolder.SetActive(false);
}
@ -467,9 +469,20 @@ namespace UnityExplorer.CacheObject
public struct ValueStateArgs
{
public ValueStateArgs(bool valueActive = true, bool valueRichText = true, Color? valueColor = null,
bool typeLabelActive = false, bool toggleActive = false, bool inputActive = false, bool applyActive = false,
bool inspectActive = false, bool subContentButtonActive = false)
public static ValueStateArgs Default { get; } = new(true);
public Color valueColor;
public bool valueActive, valueRichText, typeLabelActive, toggleActive, inputActive, applyActive, inspectActive, subContentButtonActive;
public ValueStateArgs(bool valueActive = true,
bool valueRichText = true,
Color? valueColor = null,
bool typeLabelActive = false,
bool toggleActive = false,
bool inputActive = false,
bool applyActive = false,
bool inspectActive = false,
bool subContentButtonActive = false)
{
this.valueActive = valueActive;
this.valueRichText = valueRichText;
@ -481,14 +494,6 @@ namespace UnityExplorer.CacheObject
this.inspectActive = inspectActive;
this.subContentButtonActive = subContentButtonActive;
}
public static ValueStateArgs Default => _default;
private static ValueStateArgs _default = new ValueStateArgs(true);
public bool valueActive, valueRichText, typeLabelActive, toggleActive,
inputActive, applyActive, inspectActive, subContentButtonActive;
public Color valueColor;
}
}
}

View File

@ -40,13 +40,11 @@ namespace UnityExplorer.CacheObject
ret = PropertyInfo.GetValue(DeclaringInstance, this.Evaluator.TryParseArguments());
else
ret = PropertyInfo.GetValue(DeclaringInstance, null);
HadException = false;
LastException = null;
return ret;
}
catch (Exception ex)
{
HadException = true;
LastException = ex;
return null;
}

View File

@ -31,8 +31,9 @@ namespace UnityExplorer.CacheObject.IValues
{
base.OnBorrowed(owner);
inputField.Component.readOnly = !owner.CanWrite;
ApplyButton.Component.gameObject.SetActive(owner.CanWrite);
bool canWrite = owner.CanWrite && owner.State != ValueState.Exception;
inputField.Component.readOnly = !canWrite;
ApplyButton.Component.gameObject.SetActive(canWrite);
SaveFilePath.Text = Path.Combine(ConfigManager.Default_Output_Path.Value, "untitled.txt");
}
@ -47,6 +48,9 @@ namespace UnityExplorer.CacheObject.IValues
public override void SetValue(object value)
{
if (CurrentOwner.State == ValueState.Exception)
value = CurrentOwner.LastException.ToString();
RealValue = value as string;
SaveFileRow.SetActive(IsStringTooLong(RealValue));

View File

@ -16,22 +16,16 @@ namespace UnityExplorer.CacheObject.IValues
{
public static Type GetIValueTypeForState(ValueState state)
{
switch (state)
return state switch
{
case ValueState.String:
return typeof(InteractiveString);
case ValueState.Enum:
return typeof(InteractiveEnum);
case ValueState.Collection:
return typeof(InteractiveList);
case ValueState.Dictionary:
return typeof(InteractiveDictionary);
case ValueState.ValueStruct:
return typeof(InteractiveValueStruct);
case ValueState.Color:
return typeof(InteractiveColor);
default: return null;
}
ValueState.Exception or ValueState.String => typeof(InteractiveString),
ValueState.Enum => typeof(InteractiveEnum),
ValueState.Collection => typeof(InteractiveList),
ValueState.Dictionary => typeof(InteractiveDictionary),
ValueState.ValueStruct => typeof(InteractiveValueStruct),
ValueState.Color => typeof(InteractiveColor),
_ => null,
};
}
public GameObject UIRoot { get; set; }
@ -39,28 +33,28 @@ namespace UnityExplorer.CacheObject.IValues
public virtual bool CanWrite => this.CurrentOwner.CanWrite;
public CacheObjectBase CurrentOwner => m_owner;
private CacheObjectBase m_owner;
public CacheObjectBase CurrentOwner => owner;
private CacheObjectBase owner;
public bool PendingValueWanted;
public virtual void OnBorrowed(CacheObjectBase owner)
{
if (this.m_owner != null)
if (this.owner != null)
{
ExplorerCore.LogWarning("Setting an IValue's owner but there is already one set. Maybe it wasn't cleaned up?");
ReleaseFromOwner();
}
this.m_owner = owner;
this.owner = owner;
}
public virtual void ReleaseFromOwner()
{
if (this.m_owner == null)
if (this.owner == null)
return;
this.m_owner = null;
this.owner = null;
}
public abstract void SetValue(object value);

View File

@ -9,29 +9,26 @@ using UnhollowerRuntimeLib;
namespace UnityExplorer
{
// Handles all Behaviour update calls for UnityExplorer (Update, FixedUpdate, OnPostRender).
// Basically just a wrapper which calls the corresponding methods in ExplorerCore.
public class ExplorerBehaviour : MonoBehaviour
{
internal static ExplorerBehaviour Instance { get; private set; }
#if CPP
public ExplorerBehaviour(IntPtr ptr) : base(ptr) { }
#endif
internal static void Setup()
{
#if CPP
ClassInjector.RegisterTypeInIl2Cpp<ExplorerBehaviour>();
#endif
var obj = new GameObject("ExplorerBehaviour");
GameObject.DontDestroyOnLoad(obj);
obj.hideFlags |= HideFlags.HideAndDontSave;
GameObject obj = new("ExplorerBehaviour");
DontDestroyOnLoad(obj);
obj.hideFlags = HideFlags.HideAndDontSave;
Instance = obj.AddComponent<ExplorerBehaviour>();
}
#if CPP
public ExplorerBehaviour(IntPtr ptr) : base(ptr) { }
#endif
internal void Update()
{
ExplorerCore.Update();

View File

@ -15,7 +15,7 @@ namespace UnityExplorer
public static class ExplorerCore
{
public const string NAME = "UnityExplorer";
public const string VERSION = "4.6.2";
public const string VERSION = "4.6.4";
public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.unityexplorer";

View File

@ -28,6 +28,8 @@ namespace UnityExplorer.Tests
public static object LiterallyAnything = null;
public static string Exception => throw new Exception("This is a test.");
// Test enumerables
public static int[,,] MultiDimensionalArray = new int[45, 45, 45];
public static List<object> ListOfInts;

View File

@ -10,7 +10,7 @@ namespace UnityExplorer.UI.Widgets
{
public TransformTree Tree { get; }
public Transform Value { get; private set; }
public int InstanceID { get; private set; }
public int InstanceID { get; }
public CachedTransform Parent { get; internal set; }
public int Depth { get; internal set; }
@ -23,10 +23,11 @@ namespace UnityExplorer.UI.Widgets
public CachedTransform(TransformTree tree, Transform transform, int depth, CachedTransform parent = null)
{
InstanceID = transform.GetInstanceID();
Tree = tree;
Value = transform;
Parent = parent;
InstanceID = transform.GetInstanceID();
SiblingIndex = transform.GetSiblingIndex();
Update(transform, depth);
}

View File

@ -29,7 +29,6 @@ namespace UnityExplorer.UI.Widgets
public Action<GameObject> OnGameObjectClicked;
public CachedTransform cachedTransform;
public int cellIndex;
public GameObject UIRoot { get; set; }
public RectTransform Rect { get; set; }
@ -53,7 +52,7 @@ namespace UnityExplorer.UI.Widgets
UIRoot.SetActive(false);
}
public void ConfigureCell(CachedTransform cached, int cellIndex)
public void ConfigureCell(CachedTransform cached)
{
if (cached == null)
{
@ -64,7 +63,6 @@ namespace UnityExplorer.UI.Widgets
if (!Enabled)
Enable();
this.cellIndex = cellIndex;
cachedTransform = cached;
spacer.minWidth = cached.Depth * 15;

View File

@ -22,8 +22,8 @@ namespace UnityExplorer.UI.Widgets
// - Remove(object)
// - set_Item[object]
// These two methods have extremely bad performance due to using IndexOfKey(), which iterates the whole dictionary.
// Currently we do not use either of these methods, so everything should be constant time hash lookups.
// We DO make use of get_Item[object], get_Item[index], Add, Insert and RemoveAt, which OrderedDictionary perfectly meets our needs for.
// Currently we do not use either of these methods, so everything should be constant time lookups.
// We DO make use of get_Item[object], get_Item[index], Add, Insert, Contains and RemoveAt, which OrderedDictionary meets our needs for.
/// <summary>
/// Key: UnityEngine.Transform instance ID<br/>
/// Value: CachedTransform
@ -182,31 +182,38 @@ namespace UnityExplorer.UI.Widgets
traversedThisFrame.Reset();
traversedThisFrame.Start();
IEnumerable<GameObject> rootObjects = GetRootEntriesMethod.Invoke();
refreshCoroutine = RuntimeHelper.StartCoroutine(RefreshCoroutine(rootObjects, andRefreshUI, jumpToTop, oneShot));
refreshCoroutine = RuntimeHelper.StartCoroutine(RefreshCoroutine(andRefreshUI, jumpToTop, oneShot));
}
// Coroutine for batched updates, max 2000 gameobjects per frame so FPS doesn't get tanked when there is like 100k gameobjects.
// if "oneShot", then this will NOT be batched (if we need an immediate full update).
IEnumerator RefreshCoroutine(IEnumerable<GameObject> rootObjects, bool andRefreshUI, bool jumpToTop, bool oneShot)
IEnumerator RefreshCoroutine(bool andRefreshUI, bool jumpToTop, bool oneShot)
{
// Instead of doing string.IsNullOrEmpty(CurrentFilter) many times, let's just do it once per update.
bool filtering = Filtering;
IEnumerable<GameObject> rootObjects = GetRootEntriesMethod();
foreach (var gameObj in rootObjects)
{
if (gameObj)
if (!gameObj)
continue;
IEnumerator enumerator = Traverse(gameObj.transform, null, 0, oneShot, filtering);
while (enumerator.MoveNext())
{
var enumerator = Traverse(gameObj.transform, null, 0, oneShot);
while (enumerator.MoveNext())
{
if (!oneShot)
yield return enumerator.Current;
}
if (!oneShot)
yield return enumerator.Current;
}
}
// Prune displayed transforms that we didnt visit in that traverse
for (int i = cachedTransforms.Count - 1; i >= 0; i--)
{
if (traversedThisFrame.ElapsedMilliseconds > 2)
{
yield return null;
traversedThisFrame.Reset();
traversedThisFrame.Start();
}
var cached = (CachedTransform)cachedTransforms[i];
if (!visited.Contains(cached.InstanceID))
{
@ -224,9 +231,8 @@ namespace UnityExplorer.UI.Widgets
// Recursive method to check a Transform and its children (if expanded).
// Parent and depth can be null/default.
private IEnumerator Traverse(Transform transform, CachedTransform parent, int depth, bool oneShot)
private IEnumerator Traverse(Transform transform, CachedTransform parent, int depth, bool oneShot, bool filtering)
{
// Let's only tank 2ms of each frame (60->53fps)
if (traversedThisFrame.ElapsedMilliseconds > 2)
{
yield return null;
@ -236,21 +242,20 @@ namespace UnityExplorer.UI.Widgets
int instanceID = transform.GetInstanceID();
// Unlikely, but since this method is async it could theoretically happen in extremely rare circumstances
if (visited.Contains(instanceID))
yield break;
if (Filtering)
if (filtering)
{
if (!FilterHierarchy(transform))
yield break;
visited.Add(instanceID);
if (!autoExpandedIDs.Contains(instanceID))
autoExpandedIDs.Add(instanceID);
}
else
visited.Add(instanceID);
visited.Add(instanceID);
CachedTransform cached;
if (cachedTransforms.Contains(instanceID))
@ -288,7 +293,7 @@ namespace UnityExplorer.UI.Widgets
{
for (int i = 0; i < transform.childCount; i++)
{
var enumerator = Traverse(transform.GetChild(i), cached, depth + 1, oneShot);
var enumerator = Traverse(transform.GetChild(i), cached, depth + 1, oneShot, filtering);
while (enumerator.MoveNext())
{
if (!oneShot)
@ -317,7 +322,7 @@ namespace UnityExplorer.UI.Widgets
{
if (index < cachedTransforms.Count)
{
cell.ConfigureCell((CachedTransform)cachedTransforms[index], index);
cell.ConfigureCell((CachedTransform)cachedTransforms[index]);
if (Filtering)
{
if (cell.cachedTransform.Name.ContainsIgnoreCase(currentFilter))

View File

@ -175,13 +175,13 @@
<Private>False</Private>
</Reference>
<Reference Include="UniverseLib.Mono">
<HintPath>packages\UniverseLib.1.2.17\lib\net35\UniverseLib.Mono.dll</HintPath>
<HintPath>packages\UniverseLib.1.2.18\lib\net35\UniverseLib.Mono.dll</HintPath>
</Reference>
</ItemGroup>
<!-- Il2Cpp refs -->
<ItemGroup Condition="'$(IsCpp)'=='true'">
<Reference Include="UniverseLib.IL2CPP">
<HintPath>packages\UniverseLib.1.2.17\lib\net472\UniverseLib.IL2CPP.dll</HintPath>
<HintPath>packages\UniverseLib.1.2.18\lib\net472\UniverseLib.IL2CPP.dll</HintPath>
</Reference>
<Reference Include="UnhollowerBaseLib, Version=0.4.22.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>packages\Il2CppAssemblyUnhollower.BaseLib.0.4.22\lib\net472\UnhollowerBaseLib.dll</HintPath>

View File

@ -6,6 +6,6 @@
<package id="ILRepack.Lib.MSBuild.Task" version="2.0.18.2" targetFramework="net35" />
<package id="Mono.Cecil" version="0.10.4" targetFramework="net35" />
<package id="Samboy063.Tomlet" version="3.1.3" targetFramework="net472" />
<package id="UniverseLib" version="1.2.17" targetFramework="net35" />
<package id="UniverseLib" version="1.2.18" targetFramework="net35" />
<package id="UniverseLib.Analyzers" version="1.0.3" targetFramework="net35" developmentDependency="true" />
</packages>