Skip to content
This repository was archived by the owner on May 9, 2023. It is now read-only.

Commit f00134b

Browse files
committed
Add "one-shot" option for TransformTree updating
1 parent 3b6b976 commit f00134b

File tree

4 files changed

+76
-41
lines changed

4 files changed

+76
-41
lines changed

src/Inspectors/GameObjectInspector.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public void ChangeTarget(GameObject newTarget)
8282
this.Target = newTarget;
8383
GOControls.UpdateGameObjectInfo(true, true);
8484
GOControls.UpdateTransformControlValues(true);
85-
TransformTree.RefreshData(true, false, true);
85+
TransformTree.RefreshData(true, false, true, false);
8686
UpdateComponents();
8787
}
8888

@@ -109,7 +109,7 @@ public override void Update()
109109

110110
GOControls.UpdateGameObjectInfo(false, false);
111111

112-
TransformTree.RefreshData(true, false, false);
112+
TransformTree.RefreshData(true, false, false, false);
113113
UpdateComponents();
114114
}
115115
}
@@ -220,7 +220,7 @@ private void OnAddChildClicked(string input)
220220
var newObject = new GameObject(input);
221221
newObject.transform.parent = GOTarget.transform;
222222

223-
TransformTree.RefreshData(true, false, true);
223+
TransformTree.RefreshData(true, false, true, false);
224224
}
225225

226226
private void OnAddComponentClicked(string input)

src/ObjectExplorer/SceneExplorer.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public void Update()
6464
public void UpdateTree()
6565
{
6666
SceneHandler.Update();
67-
Tree.RefreshData(true, false, false);
67+
Tree.RefreshData(true, false, false, false);
6868
}
6969

7070
public void JumpToTransform(Transform transform)
@@ -94,7 +94,7 @@ private void OnSceneSelectionDropdownChanged(int value)
9494

9595
SceneHandler.SelectedScene = SceneHandler.LoadedScenes[value];
9696
SceneHandler.Update();
97-
Tree.RefreshData(true, true, true);
97+
Tree.RefreshData(true, true, true, false);
9898
OnSelectedSceneChanged(SceneHandler.SelectedScene.Value);
9999
}
100100

@@ -158,7 +158,7 @@ private void OnFilterInput(string input)
158158
}
159159

160160
Tree.CurrentFilter = input;
161-
Tree.RefreshData(true, false, true);
161+
Tree.RefreshData(true, false, true, false);
162162
}
163163

164164
private void TryLoadScene(LoadSceneMode mode, Dropdown allSceneDrop)
@@ -259,7 +259,7 @@ public override void ConstructUI(GameObject content)
259259

260260
Tree = new TransformTree(scrollPool, GetRootEntries);
261261
Tree.Init();
262-
Tree.RefreshData(true, true, true);
262+
Tree.RefreshData(true, true, true, false);
263263
//scrollPool.Viewport.GetComponent<Mask>().enabled = false;
264264
//UIRoot.GetComponent<Mask>().enabled = false;
265265

src/UI/Widgets/TransformTree/CachedTransform.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class CachedTransform
1919
public bool Enabled { get; internal set; }
2020
public int SiblingIndex { get; internal set; }
2121

22-
public bool Expanded => Tree.IsCellExpanded(InstanceID);
22+
public bool Expanded => Tree.IsTransformExpanded(InstanceID);
2323

2424
public CachedTransform(TransformTree tree, Transform transform, int depth, CachedTransform parent = null)
2525
{

src/UI/Widgets/TransformTree/TransformTree.cs

Lines changed: 68 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ public class TransformTree : ICellPoolDataSource<TransformCell>
1717

1818
public ScrollPool<TransformCell> ScrollPool;
1919

20+
// IMPORTANT CAVEAT WITH OrderedDictionary:
21+
// While the performance is mostly good, there are two methods we should NEVER use:
22+
// - Remove(object)
23+
// - set_Item[object]
24+
// These two methods have extremely bad performance due to using IndexOfKey(), which iterates the whole dictionary.
25+
// Currently we do not use either of these methods, so everything should be constant time hash lookups.
26+
// We DO make use of get_Item[object], get_Item[index], Add, Insert and RemoveAt, which OrderedDictionary perfectly meets our needs for.
2027
/// <summary>
2128
/// Key: UnityEngine.Transform instance ID<br/>
2229
/// Value: CachedTransform
@@ -27,13 +34,19 @@ public class TransformTree : ICellPoolDataSource<TransformCell>
2734
private readonly HashSet<int> expandedInstanceIDs = new();
2835
private readonly HashSet<int> autoExpandedIDs = new();
2936

37+
// state for Traverse parse
3038
private readonly HashSet<int> visited = new();
31-
private bool needRefresh;
39+
private bool needRefreshUI;
3240
private int displayIndex;
3341
int prevDisplayIndex;
3442

43+
private Coroutine refreshCoroutine;
44+
private readonly Stopwatch traversedThisFrame = new();
45+
46+
// ScrollPool item count. PrevDisplayIndex is the highest index + 1 from our last traverse.
3547
public int ItemCount => prevDisplayIndex;
3648

49+
// Search filter
3750
public bool Filtering => !string.IsNullOrEmpty(currentFilter);
3851
private bool wasFiltering;
3952

@@ -54,35 +67,52 @@ public string CurrentFilter
5467
}
5568
private string currentFilter;
5669

57-
private Coroutine refreshCoroutine;
58-
private readonly Stopwatch traversedThisFrame = new();
59-
6070
public TransformTree(ScrollPool<TransformCell> scrollPool, Func<IEnumerable<GameObject>> getRootEntriesMethod)
6171
{
6272
ScrollPool = scrollPool;
6373
GetRootEntriesMethod = getRootEntriesMethod;
6474
}
6575

76+
// Initialize and reset
77+
78+
// Must be called externally from owner of this TransformTree
6679
public void Init()
6780
{
6881
ScrollPool.Initialize(this);
6982
}
7083

84+
// Called to completely reset the tree, ie. switching inspected GameObject
85+
public void Rebuild()
86+
{
87+
autoExpandedIDs.Clear();
88+
expandedInstanceIDs.Clear();
89+
90+
RefreshData(true, true, true, false);
91+
}
92+
93+
// Called to completely wipe our data (ie, GameObject inspector returning to pool)
7194
public void Clear()
7295
{
7396
this.cachedTransforms.Clear();
7497
displayIndex = 0;
7598
autoExpandedIDs.Clear();
7699
expandedInstanceIDs.Clear();
77100
this.ScrollPool.Refresh(true, true);
101+
if (refreshCoroutine != null)
102+
{
103+
RuntimeHelper.StopCoroutine(refreshCoroutine);
104+
refreshCoroutine = null;
105+
}
78106
}
79107

80-
public bool IsCellExpanded(int instanceID)
108+
// Checks if the given Instance ID is expanded or not
109+
public bool IsTransformExpanded(int instanceID)
81110
{
82111
return Filtering ? autoExpandedIDs.Contains(instanceID)
83112
: expandedInstanceIDs.Contains(instanceID);
84113
}
85114

115+
// Jumps to a specific Transform in the tree and highlights it.
86116
public void JumpAndExpandToTransform(Transform transform)
87117
{
88118
// make sure all parents of the object are expanded
@@ -96,8 +126,9 @@ public void JumpAndExpandToTransform(Transform transform)
96126
parent = parent.parent;
97127
}
98128

99-
// Refresh cached transforms (no UI rebuild yet)
100-
RefreshData(false, false, false);
129+
// Refresh cached transforms (no UI rebuild yet).
130+
// Stop existing coroutine and do it oneshot.
131+
RefreshData(false, false, true, true);
101132

102133
int transformID = transform.GetInstanceID();
103134

@@ -130,15 +161,9 @@ private IEnumerator HighlightCellCoroutine(TransformCell cell)
130161
button.OnDeselect(null);
131162
}
132163

133-
public void Rebuild()
134-
{
135-
autoExpandedIDs.Clear();
136-
expandedInstanceIDs.Clear();
137-
138-
RefreshData(true, true, true);
139-
}
140-
141-
public void RefreshData(bool andRefreshUI, bool jumpToTop, bool stopExistingCoroutine)
164+
// Perform a Traverse and optionally refresh the ScrollPool as well.
165+
// If oneShot, then this happens instantly with no yield.
166+
public void RefreshData(bool andRefreshUI, bool jumpToTop, bool stopExistingCoroutine, bool oneShot)
142167
{
143168
if (refreshCoroutine != null)
144169
{
@@ -153,25 +178,29 @@ public void RefreshData(bool andRefreshUI, bool jumpToTop, bool stopExistingCoro
153178

154179
visited.Clear();
155180
displayIndex = 0;
156-
needRefresh = false;
181+
needRefreshUI = false;
157182
traversedThisFrame.Reset();
158183
traversedThisFrame.Start();
159184

160185
IEnumerable<GameObject> rootObjects = GetRootEntriesMethod.Invoke();
161186

162-
refreshCoroutine = RuntimeHelper.StartCoroutine(RefreshCoroutine(rootObjects, andRefreshUI, jumpToTop));
187+
refreshCoroutine = RuntimeHelper.StartCoroutine(RefreshCoroutine(rootObjects, andRefreshUI, jumpToTop, oneShot));
163188
}
164189

165-
private IEnumerator RefreshCoroutine(IEnumerable<GameObject> rootObjects, bool andRefreshUI, bool jumpToTop)
190+
// Coroutine for batched updates, max 2000 gameobjects per frame so FPS doesn't get tanked when there is like 100k gameobjects.
191+
// if "oneShot", then this will NOT be batched (if we need an immediate full update).
192+
IEnumerator RefreshCoroutine(IEnumerable<GameObject> rootObjects, bool andRefreshUI, bool jumpToTop, bool oneShot)
166193
{
167-
var thisCoro = refreshCoroutine;
168194
foreach (var gameObj in rootObjects)
169195
{
170196
if (gameObj)
171197
{
172-
var enumerator = Traverse(gameObj.transform);
198+
var enumerator = Traverse(gameObj.transform, null, 0, oneShot);
173199
while (enumerator.MoveNext())
174-
yield return enumerator.Current;
200+
{
201+
if (!oneShot)
202+
yield return enumerator.Current;
203+
}
175204
}
176205
}
177206

@@ -182,17 +211,20 @@ private IEnumerator RefreshCoroutine(IEnumerable<GameObject> rootObjects, bool a
182211
if (!visited.Contains(cached.InstanceID))
183212
{
184213
cachedTransforms.RemoveAt(i);
185-
needRefresh = true;
214+
needRefreshUI = true;
186215
}
187216
}
188217

189-
if (andRefreshUI && needRefresh)
218+
if (andRefreshUI && needRefreshUI)
190219
ScrollPool.Refresh(true, jumpToTop);
191220

192221
prevDisplayIndex = displayIndex;
193-
}
222+
refreshCoroutine = null;
223+
}
194224

195-
private IEnumerator Traverse(Transform transform, CachedTransform parent = null, int depth = 0)
225+
// Recursive method to check a Transform and its children (if expanded).
226+
// Parent and depth can be null/default.
227+
private IEnumerator Traverse(Transform transform, CachedTransform parent, int depth, bool oneShot)
196228
{
197229
// Let's only tank 2ms of each frame (60->53fps)
198230
if (traversedThisFrame.ElapsedMilliseconds > 2)
@@ -227,7 +259,7 @@ private IEnumerator Traverse(Transform transform, CachedTransform parent = null,
227259
int prevSiblingIdx = cached.SiblingIndex;
228260
if (cached.Update(transform, depth))
229261
{
230-
needRefresh = true;
262+
needRefreshUI = true;
231263

232264
// If the sibling index changed, we need to shuffle it in our cached transforms list.
233265
if (prevSiblingIdx != cached.SiblingIndex)
@@ -242,7 +274,7 @@ private IEnumerator Traverse(Transform transform, CachedTransform parent = null,
242274
}
243275
else
244276
{
245-
needRefresh = true;
277+
needRefreshUI = true;
246278
cached = new CachedTransform(this, transform, depth, parent);
247279
if (cachedTransforms.Count <= displayIndex)
248280
cachedTransforms.Add(instanceID, cached);
@@ -252,13 +284,16 @@ private IEnumerator Traverse(Transform transform, CachedTransform parent = null,
252284

253285
displayIndex++;
254286

255-
if (IsCellExpanded(instanceID) && cached.Value.childCount > 0)
287+
if (IsTransformExpanded(instanceID) && cached.Value.childCount > 0)
256288
{
257289
for (int i = 0; i < transform.childCount; i++)
258290
{
259-
var enumerator = Traverse(transform.GetChild(i), cached, depth + 1);
291+
var enumerator = Traverse(transform.GetChild(i), cached, depth + 1, oneShot);
260292
while (enumerator.MoveNext())
261-
yield return enumerator.Current;
293+
{
294+
if (!oneShot)
295+
yield return enumerator.Current;
296+
}
262297
}
263298
}
264299
}
@@ -316,14 +351,14 @@ public void OnCellExpandToggled(CachedTransform cache)
316351
else
317352
expandedInstanceIDs.Add(instanceID);
318353

319-
RefreshData(true, false, true);
354+
RefreshData(true, false, true, false);
320355
}
321356

322357
public void OnCellEnableToggled(CachedTransform cache)
323358
{
324359
cache.Value.gameObject.SetActive(!cache.Value.gameObject.activeSelf);
325360

326-
RefreshData(true, false, true);
361+
RefreshData(true, false, true, false);
327362
}
328363
}
329364
}

0 commit comments

Comments
 (0)