Add "one-shot" option for TransformTree updating

This commit is contained in:
Sinai
2022-03-10 17:56:21 +11:00
parent 3b6b9768fb
commit f00134b283
4 changed files with 76 additions and 41 deletions

View File

@ -82,7 +82,7 @@ namespace UnityExplorer.Inspectors
this.Target = newTarget; this.Target = newTarget;
GOControls.UpdateGameObjectInfo(true, true); GOControls.UpdateGameObjectInfo(true, true);
GOControls.UpdateTransformControlValues(true); GOControls.UpdateTransformControlValues(true);
TransformTree.RefreshData(true, false, true); TransformTree.RefreshData(true, false, true, false);
UpdateComponents(); UpdateComponents();
} }
@ -109,7 +109,7 @@ namespace UnityExplorer.Inspectors
GOControls.UpdateGameObjectInfo(false, false); GOControls.UpdateGameObjectInfo(false, false);
TransformTree.RefreshData(true, false, false); TransformTree.RefreshData(true, false, false, false);
UpdateComponents(); UpdateComponents();
} }
} }
@ -220,7 +220,7 @@ namespace UnityExplorer.Inspectors
var newObject = new GameObject(input); var newObject = new GameObject(input);
newObject.transform.parent = GOTarget.transform; newObject.transform.parent = GOTarget.transform;
TransformTree.RefreshData(true, false, true); TransformTree.RefreshData(true, false, true, false);
} }
private void OnAddComponentClicked(string input) private void OnAddComponentClicked(string input)

View File

@ -64,7 +64,7 @@ namespace UnityExplorer.ObjectExplorer
public void UpdateTree() public void UpdateTree()
{ {
SceneHandler.Update(); SceneHandler.Update();
Tree.RefreshData(true, false, false); Tree.RefreshData(true, false, false, false);
} }
public void JumpToTransform(Transform transform) public void JumpToTransform(Transform transform)
@ -94,7 +94,7 @@ namespace UnityExplorer.ObjectExplorer
SceneHandler.SelectedScene = SceneHandler.LoadedScenes[value]; SceneHandler.SelectedScene = SceneHandler.LoadedScenes[value];
SceneHandler.Update(); SceneHandler.Update();
Tree.RefreshData(true, true, true); Tree.RefreshData(true, true, true, false);
OnSelectedSceneChanged(SceneHandler.SelectedScene.Value); OnSelectedSceneChanged(SceneHandler.SelectedScene.Value);
} }
@ -158,7 +158,7 @@ namespace UnityExplorer.ObjectExplorer
} }
Tree.CurrentFilter = input; Tree.CurrentFilter = input;
Tree.RefreshData(true, false, true); Tree.RefreshData(true, false, true, false);
} }
private void TryLoadScene(LoadSceneMode mode, Dropdown allSceneDrop) private void TryLoadScene(LoadSceneMode mode, Dropdown allSceneDrop)
@ -259,7 +259,7 @@ namespace UnityExplorer.ObjectExplorer
Tree = new TransformTree(scrollPool, GetRootEntries); Tree = new TransformTree(scrollPool, GetRootEntries);
Tree.Init(); Tree.Init();
Tree.RefreshData(true, true, true); Tree.RefreshData(true, true, true, false);
//scrollPool.Viewport.GetComponent<Mask>().enabled = false; //scrollPool.Viewport.GetComponent<Mask>().enabled = false;
//UIRoot.GetComponent<Mask>().enabled = false; //UIRoot.GetComponent<Mask>().enabled = false;

View File

@ -19,7 +19,7 @@ namespace UnityExplorer.UI.Widgets
public bool Enabled { get; internal set; } public bool Enabled { get; internal set; }
public int SiblingIndex { get; internal set; } public int SiblingIndex { get; internal set; }
public bool Expanded => Tree.IsCellExpanded(InstanceID); public bool Expanded => Tree.IsTransformExpanded(InstanceID);
public CachedTransform(TransformTree tree, Transform transform, int depth, CachedTransform parent = null) public CachedTransform(TransformTree tree, Transform transform, int depth, CachedTransform parent = null)
{ {

View File

@ -17,6 +17,13 @@ namespace UnityExplorer.UI.Widgets
public ScrollPool<TransformCell> ScrollPool; public ScrollPool<TransformCell> ScrollPool;
// IMPORTANT CAVEAT WITH OrderedDictionary:
// While the performance is mostly good, there are two methods we should NEVER use:
// - 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.
/// <summary> /// <summary>
/// Key: UnityEngine.Transform instance ID<br/> /// Key: UnityEngine.Transform instance ID<br/>
/// Value: CachedTransform /// Value: CachedTransform
@ -27,13 +34,19 @@ namespace UnityExplorer.UI.Widgets
private readonly HashSet<int> expandedInstanceIDs = new(); private readonly HashSet<int> expandedInstanceIDs = new();
private readonly HashSet<int> autoExpandedIDs = new(); private readonly HashSet<int> autoExpandedIDs = new();
// state for Traverse parse
private readonly HashSet<int> visited = new(); private readonly HashSet<int> visited = new();
private bool needRefresh; private bool needRefreshUI;
private int displayIndex; private int displayIndex;
int prevDisplayIndex; int prevDisplayIndex;
private Coroutine refreshCoroutine;
private readonly Stopwatch traversedThisFrame = new();
// ScrollPool item count. PrevDisplayIndex is the highest index + 1 from our last traverse.
public int ItemCount => prevDisplayIndex; public int ItemCount => prevDisplayIndex;
// Search filter
public bool Filtering => !string.IsNullOrEmpty(currentFilter); public bool Filtering => !string.IsNullOrEmpty(currentFilter);
private bool wasFiltering; private bool wasFiltering;
@ -54,20 +67,30 @@ namespace UnityExplorer.UI.Widgets
} }
private string currentFilter; private string currentFilter;
private Coroutine refreshCoroutine;
private readonly Stopwatch traversedThisFrame = new();
public TransformTree(ScrollPool<TransformCell> scrollPool, Func<IEnumerable<GameObject>> getRootEntriesMethod) public TransformTree(ScrollPool<TransformCell> scrollPool, Func<IEnumerable<GameObject>> getRootEntriesMethod)
{ {
ScrollPool = scrollPool; ScrollPool = scrollPool;
GetRootEntriesMethod = getRootEntriesMethod; GetRootEntriesMethod = getRootEntriesMethod;
} }
// Initialize and reset
// Must be called externally from owner of this TransformTree
public void Init() public void Init()
{ {
ScrollPool.Initialize(this); ScrollPool.Initialize(this);
} }
// Called to completely reset the tree, ie. switching inspected GameObject
public void Rebuild()
{
autoExpandedIDs.Clear();
expandedInstanceIDs.Clear();
RefreshData(true, true, true, false);
}
// Called to completely wipe our data (ie, GameObject inspector returning to pool)
public void Clear() public void Clear()
{ {
this.cachedTransforms.Clear(); this.cachedTransforms.Clear();
@ -75,14 +98,21 @@ namespace UnityExplorer.UI.Widgets
autoExpandedIDs.Clear(); autoExpandedIDs.Clear();
expandedInstanceIDs.Clear(); expandedInstanceIDs.Clear();
this.ScrollPool.Refresh(true, true); this.ScrollPool.Refresh(true, true);
if (refreshCoroutine != null)
{
RuntimeHelper.StopCoroutine(refreshCoroutine);
refreshCoroutine = null;
}
} }
public bool IsCellExpanded(int instanceID) // Checks if the given Instance ID is expanded or not
public bool IsTransformExpanded(int instanceID)
{ {
return Filtering ? autoExpandedIDs.Contains(instanceID) return Filtering ? autoExpandedIDs.Contains(instanceID)
: expandedInstanceIDs.Contains(instanceID); : expandedInstanceIDs.Contains(instanceID);
} }
// Jumps to a specific Transform in the tree and highlights it.
public void JumpAndExpandToTransform(Transform transform) public void JumpAndExpandToTransform(Transform transform)
{ {
// make sure all parents of the object are expanded // make sure all parents of the object are expanded
@ -96,8 +126,9 @@ namespace UnityExplorer.UI.Widgets
parent = parent.parent; parent = parent.parent;
} }
// Refresh cached transforms (no UI rebuild yet) // Refresh cached transforms (no UI rebuild yet).
RefreshData(false, false, false); // Stop existing coroutine and do it oneshot.
RefreshData(false, false, true, true);
int transformID = transform.GetInstanceID(); int transformID = transform.GetInstanceID();
@ -130,15 +161,9 @@ namespace UnityExplorer.UI.Widgets
button.OnDeselect(null); button.OnDeselect(null);
} }
public void Rebuild() // Perform a Traverse and optionally refresh the ScrollPool as well.
{ // If oneShot, then this happens instantly with no yield.
autoExpandedIDs.Clear(); public void RefreshData(bool andRefreshUI, bool jumpToTop, bool stopExistingCoroutine, bool oneShot)
expandedInstanceIDs.Clear();
RefreshData(true, true, true);
}
public void RefreshData(bool andRefreshUI, bool jumpToTop, bool stopExistingCoroutine)
{ {
if (refreshCoroutine != null) if (refreshCoroutine != null)
{ {
@ -153,25 +178,29 @@ namespace UnityExplorer.UI.Widgets
visited.Clear(); visited.Clear();
displayIndex = 0; displayIndex = 0;
needRefresh = false; needRefreshUI = false;
traversedThisFrame.Reset(); traversedThisFrame.Reset();
traversedThisFrame.Start(); traversedThisFrame.Start();
IEnumerable<GameObject> rootObjects = GetRootEntriesMethod.Invoke(); IEnumerable<GameObject> rootObjects = GetRootEntriesMethod.Invoke();
refreshCoroutine = RuntimeHelper.StartCoroutine(RefreshCoroutine(rootObjects, andRefreshUI, jumpToTop)); refreshCoroutine = RuntimeHelper.StartCoroutine(RefreshCoroutine(rootObjects, andRefreshUI, jumpToTop, oneShot));
} }
private IEnumerator RefreshCoroutine(IEnumerable<GameObject> rootObjects, bool andRefreshUI, bool jumpToTop) // 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)
{ {
var thisCoro = refreshCoroutine;
foreach (var gameObj in rootObjects) foreach (var gameObj in rootObjects)
{ {
if (gameObj) if (gameObj)
{ {
var enumerator = Traverse(gameObj.transform); var enumerator = Traverse(gameObj.transform, null, 0, oneShot);
while (enumerator.MoveNext()) while (enumerator.MoveNext())
yield return enumerator.Current; {
if (!oneShot)
yield return enumerator.Current;
}
} }
} }
@ -182,17 +211,20 @@ namespace UnityExplorer.UI.Widgets
if (!visited.Contains(cached.InstanceID)) if (!visited.Contains(cached.InstanceID))
{ {
cachedTransforms.RemoveAt(i); cachedTransforms.RemoveAt(i);
needRefresh = true; needRefreshUI = true;
} }
} }
if (andRefreshUI && needRefresh) if (andRefreshUI && needRefreshUI)
ScrollPool.Refresh(true, jumpToTop); ScrollPool.Refresh(true, jumpToTop);
prevDisplayIndex = displayIndex; prevDisplayIndex = displayIndex;
} refreshCoroutine = null;
}
private IEnumerator Traverse(Transform transform, CachedTransform parent = null, int depth = 0) // 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)
{ {
// Let's only tank 2ms of each frame (60->53fps) // Let's only tank 2ms of each frame (60->53fps)
if (traversedThisFrame.ElapsedMilliseconds > 2) if (traversedThisFrame.ElapsedMilliseconds > 2)
@ -227,7 +259,7 @@ namespace UnityExplorer.UI.Widgets
int prevSiblingIdx = cached.SiblingIndex; int prevSiblingIdx = cached.SiblingIndex;
if (cached.Update(transform, depth)) if (cached.Update(transform, depth))
{ {
needRefresh = true; needRefreshUI = true;
// If the sibling index changed, we need to shuffle it in our cached transforms list. // If the sibling index changed, we need to shuffle it in our cached transforms list.
if (prevSiblingIdx != cached.SiblingIndex) if (prevSiblingIdx != cached.SiblingIndex)
@ -242,7 +274,7 @@ namespace UnityExplorer.UI.Widgets
} }
else else
{ {
needRefresh = true; needRefreshUI = true;
cached = new CachedTransform(this, transform, depth, parent); cached = new CachedTransform(this, transform, depth, parent);
if (cachedTransforms.Count <= displayIndex) if (cachedTransforms.Count <= displayIndex)
cachedTransforms.Add(instanceID, cached); cachedTransforms.Add(instanceID, cached);
@ -252,13 +284,16 @@ namespace UnityExplorer.UI.Widgets
displayIndex++; displayIndex++;
if (IsCellExpanded(instanceID) && cached.Value.childCount > 0) if (IsTransformExpanded(instanceID) && cached.Value.childCount > 0)
{ {
for (int i = 0; i < transform.childCount; i++) for (int i = 0; i < transform.childCount; i++)
{ {
var enumerator = Traverse(transform.GetChild(i), cached, depth + 1); var enumerator = Traverse(transform.GetChild(i), cached, depth + 1, oneShot);
while (enumerator.MoveNext()) while (enumerator.MoveNext())
yield return enumerator.Current; {
if (!oneShot)
yield return enumerator.Current;
}
} }
} }
} }
@ -316,14 +351,14 @@ namespace UnityExplorer.UI.Widgets
else else
expandedInstanceIDs.Add(instanceID); expandedInstanceIDs.Add(instanceID);
RefreshData(true, false, true); RefreshData(true, false, true, false);
} }
public void OnCellEnableToggled(CachedTransform cache) public void OnCellEnableToggled(CachedTransform cache)
{ {
cache.Value.gameObject.SetActive(!cache.Value.gameObject.activeSelf); cache.Value.gameObject.SetActive(!cache.Value.gameObject.activeSelf);
RefreshData(true, false, true); RefreshData(true, false, true, false);
} }
} }
} }