@@ -17,6 +17,13 @@ public class TransformTree : ICellPoolDataSource<TransformCell>
17
17
18
18
public ScrollPool < TransformCell > ScrollPool ;
19
19
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.
20
27
/// <summary>
21
28
/// Key: UnityEngine.Transform instance ID<br/>
22
29
/// Value: CachedTransform
@@ -27,13 +34,19 @@ public class TransformTree : ICellPoolDataSource<TransformCell>
27
34
private readonly HashSet < int > expandedInstanceIDs = new ( ) ;
28
35
private readonly HashSet < int > autoExpandedIDs = new ( ) ;
29
36
37
+ // state for Traverse parse
30
38
private readonly HashSet < int > visited = new ( ) ;
31
- private bool needRefresh ;
39
+ private bool needRefreshUI ;
32
40
private int displayIndex ;
33
41
int prevDisplayIndex ;
34
42
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.
35
47
public int ItemCount => prevDisplayIndex ;
36
48
49
+ // Search filter
37
50
public bool Filtering => ! string . IsNullOrEmpty ( currentFilter ) ;
38
51
private bool wasFiltering ;
39
52
@@ -54,35 +67,52 @@ public string CurrentFilter
54
67
}
55
68
private string currentFilter ;
56
69
57
- private Coroutine refreshCoroutine ;
58
- private readonly Stopwatch traversedThisFrame = new ( ) ;
59
-
60
70
public TransformTree ( ScrollPool < TransformCell > scrollPool , Func < IEnumerable < GameObject > > getRootEntriesMethod )
61
71
{
62
72
ScrollPool = scrollPool ;
63
73
GetRootEntriesMethod = getRootEntriesMethod ;
64
74
}
65
75
76
+ // Initialize and reset
77
+
78
+ // Must be called externally from owner of this TransformTree
66
79
public void Init ( )
67
80
{
68
81
ScrollPool . Initialize ( this ) ;
69
82
}
70
83
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)
71
94
public void Clear ( )
72
95
{
73
96
this . cachedTransforms . Clear ( ) ;
74
97
displayIndex = 0 ;
75
98
autoExpandedIDs . Clear ( ) ;
76
99
expandedInstanceIDs . Clear ( ) ;
77
100
this . ScrollPool . Refresh ( true , true ) ;
101
+ if ( refreshCoroutine != null )
102
+ {
103
+ RuntimeHelper . StopCoroutine ( refreshCoroutine ) ;
104
+ refreshCoroutine = null ;
105
+ }
78
106
}
79
107
80
- public bool IsCellExpanded ( int instanceID )
108
+ // Checks if the given Instance ID is expanded or not
109
+ public bool IsTransformExpanded ( int instanceID )
81
110
{
82
111
return Filtering ? autoExpandedIDs . Contains ( instanceID )
83
112
: expandedInstanceIDs . Contains ( instanceID ) ;
84
113
}
85
114
115
+ // Jumps to a specific Transform in the tree and highlights it.
86
116
public void JumpAndExpandToTransform ( Transform transform )
87
117
{
88
118
// make sure all parents of the object are expanded
@@ -96,8 +126,9 @@ public void JumpAndExpandToTransform(Transform transform)
96
126
parent = parent . parent ;
97
127
}
98
128
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 ) ;
101
132
102
133
int transformID = transform . GetInstanceID ( ) ;
103
134
@@ -130,15 +161,9 @@ private IEnumerator HighlightCellCoroutine(TransformCell cell)
130
161
button . OnDeselect ( null ) ;
131
162
}
132
163
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 )
142
167
{
143
168
if ( refreshCoroutine != null )
144
169
{
@@ -153,25 +178,29 @@ public void RefreshData(bool andRefreshUI, bool jumpToTop, bool stopExistingCoro
153
178
154
179
visited . Clear ( ) ;
155
180
displayIndex = 0 ;
156
- needRefresh = false ;
181
+ needRefreshUI = false ;
157
182
traversedThisFrame . Reset ( ) ;
158
183
traversedThisFrame . Start ( ) ;
159
184
160
185
IEnumerable < GameObject > rootObjects = GetRootEntriesMethod . Invoke ( ) ;
161
186
162
- refreshCoroutine = RuntimeHelper . StartCoroutine ( RefreshCoroutine ( rootObjects , andRefreshUI , jumpToTop ) ) ;
187
+ refreshCoroutine = RuntimeHelper . StartCoroutine ( RefreshCoroutine ( rootObjects , andRefreshUI , jumpToTop , oneShot ) ) ;
163
188
}
164
189
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 )
166
193
{
167
- var thisCoro = refreshCoroutine ;
168
194
foreach ( var gameObj in rootObjects )
169
195
{
170
196
if ( gameObj )
171
197
{
172
- var enumerator = Traverse ( gameObj . transform ) ;
198
+ var enumerator = Traverse ( gameObj . transform , null , 0 , oneShot ) ;
173
199
while ( enumerator . MoveNext ( ) )
174
- yield return enumerator . Current ;
200
+ {
201
+ if ( ! oneShot )
202
+ yield return enumerator . Current ;
203
+ }
175
204
}
176
205
}
177
206
@@ -182,17 +211,20 @@ private IEnumerator RefreshCoroutine(IEnumerable<GameObject> rootObjects, bool a
182
211
if ( ! visited . Contains ( cached . InstanceID ) )
183
212
{
184
213
cachedTransforms . RemoveAt ( i ) ;
185
- needRefresh = true ;
214
+ needRefreshUI = true ;
186
215
}
187
216
}
188
217
189
- if ( andRefreshUI && needRefresh )
218
+ if ( andRefreshUI && needRefreshUI )
190
219
ScrollPool . Refresh ( true , jumpToTop ) ;
191
220
192
221
prevDisplayIndex = displayIndex ;
193
- }
222
+ refreshCoroutine = null ;
223
+ }
194
224
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 )
196
228
{
197
229
// Let's only tank 2ms of each frame (60->53fps)
198
230
if ( traversedThisFrame . ElapsedMilliseconds > 2 )
@@ -227,7 +259,7 @@ private IEnumerator Traverse(Transform transform, CachedTransform parent = null,
227
259
int prevSiblingIdx = cached . SiblingIndex ;
228
260
if ( cached . Update ( transform , depth ) )
229
261
{
230
- needRefresh = true ;
262
+ needRefreshUI = true ;
231
263
232
264
// If the sibling index changed, we need to shuffle it in our cached transforms list.
233
265
if ( prevSiblingIdx != cached . SiblingIndex )
@@ -242,7 +274,7 @@ private IEnumerator Traverse(Transform transform, CachedTransform parent = null,
242
274
}
243
275
else
244
276
{
245
- needRefresh = true ;
277
+ needRefreshUI = true ;
246
278
cached = new CachedTransform ( this , transform , depth , parent ) ;
247
279
if ( cachedTransforms . Count <= displayIndex )
248
280
cachedTransforms . Add ( instanceID , cached ) ;
@@ -252,13 +284,16 @@ private IEnumerator Traverse(Transform transform, CachedTransform parent = null,
252
284
253
285
displayIndex ++ ;
254
286
255
- if ( IsCellExpanded ( instanceID ) && cached . Value . childCount > 0 )
287
+ if ( IsTransformExpanded ( instanceID ) && cached . Value . childCount > 0 )
256
288
{
257
289
for ( int i = 0 ; i < transform . childCount ; i ++ )
258
290
{
259
- var enumerator = Traverse ( transform . GetChild ( i ) , cached , depth + 1 ) ;
291
+ var enumerator = Traverse ( transform . GetChild ( i ) , cached , depth + 1 , oneShot ) ;
260
292
while ( enumerator . MoveNext ( ) )
261
- yield return enumerator . Current ;
293
+ {
294
+ if ( ! oneShot )
295
+ yield return enumerator . Current ;
296
+ }
262
297
}
263
298
}
264
299
}
@@ -316,14 +351,14 @@ public void OnCellExpandToggled(CachedTransform cache)
316
351
else
317
352
expandedInstanceIDs . Add ( instanceID ) ;
318
353
319
- RefreshData ( true , false , true ) ;
354
+ RefreshData ( true , false , true , false ) ;
320
355
}
321
356
322
357
public void OnCellEnableToggled ( CachedTransform cache )
323
358
{
324
359
cache . Value . gameObject . SetActive ( ! cache . Value . gameObject . activeSelf ) ;
325
360
326
- RefreshData ( true , false , true ) ;
361
+ RefreshData ( true , false , true , false ) ;
327
362
}
328
363
}
329
364
}
0 commit comments