mirror of
https://github.com/GrahamKracker/UnityExplorer.git
synced 2025-07-01 19:13:03 +08:00
Add "one-shot" option for TransformTree updating
This commit is contained in:
@ -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)
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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,27 +178,31 @@ 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())
|
||||||
|
{
|
||||||
|
if (!oneShot)
|
||||||
yield return enumerator.Current;
|
yield return enumerator.Current;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Prune displayed transforms that we didnt visit in that traverse
|
// Prune displayed transforms that we didnt visit in that traverse
|
||||||
for (int i = cachedTransforms.Count - 1; i >= 0; i--)
|
for (int i = cachedTransforms.Count - 1; i >= 0; i--)
|
||||||
@ -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,16 +284,19 @@ 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())
|
||||||
|
{
|
||||||
|
if (!oneShot)
|
||||||
yield return enumerator.Current;
|
yield return enumerator.Current;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool FilterHierarchy(Transform obj)
|
private bool FilterHierarchy(Transform obj)
|
||||||
{
|
{
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user