@@ -129,8 +129,11 @@ public void Refresh( string path )
129
129
private readonly Dictionary < Type , VariableGetterHolder [ ] > typeToVariables = new Dictionary < Type , VariableGetterHolder [ ] > ( 4096 ) ;
130
130
// An optimization to search an object only once (key is a hash of the searched object)
131
131
private readonly Dictionary < string , ReferenceNode > searchedObjects = new Dictionary < string , ReferenceNode > ( 32768 ) ;
132
+ // An optimization to fetch an animation clip's curve bindings only once
133
+ private readonly Dictionary < AnimationClip , EditorCurveBinding [ ] > animationClipUniqueBindings = new Dictionary < AnimationClip , EditorCurveBinding [ ] > ( 256 ) ;
132
134
// An optimization to fetch the dependencies of an asset only once (key is the path of the asset)
133
135
private Dictionary < string , CacheEntry > assetDependencyCache ;
136
+ private CacheEntry lastRefreshedCacheEntry ;
134
137
135
138
// Dictionary to quickly find the function to search a specific type with
136
139
private Dictionary < Type , Func < Object , ReferenceNode > > typeToSearchFunction ;
@@ -145,6 +148,7 @@ public void Refresh( string path )
145
148
private bool searchMaterialsForTexture ;
146
149
147
150
private bool searchSerializableVariablesOnly ;
151
+ private bool prevSearchSerializableVariablesOnly ;
148
152
149
153
private int searchDepthLimit ; // Depth limit for recursively searching variables of objects
150
154
@@ -168,8 +172,6 @@ public void Refresh( string path )
168
172
private readonly List < ReferenceNode > nodesPool = new List < ReferenceNode > ( 32 ) ;
169
173
private readonly List < VariableGetterHolder > validVariables = new List < VariableGetterHolder > ( 32 ) ;
170
174
171
- private readonly string reflectionNameSpace = typeof ( Assembly ) . Namespace ;
172
-
173
175
private string CachePath { get { return Application . dataPath + "/../Library/AssetUsageDetector.cache" ; } } // Path of the cache file
174
176
175
177
// Search for references!
@@ -235,14 +237,16 @@ public SearchResult Run( Parameters searchParameters )
235
237
this . fieldModifiers = searchParameters . fieldModifiers | BindingFlags . Instance | BindingFlags . DeclaredOnly ;
236
238
this . propertyModifiers = searchParameters . propertyModifiers | BindingFlags . Instance | BindingFlags . DeclaredOnly ;
237
239
this . searchDepthLimit = searchParameters . searchDepthLimit ;
240
+ this . searchSerializableVariablesOnly = ! searchParameters . searchNonSerializableVariables ;
238
241
239
242
// Initialize commonly used variables
240
243
searchResult = new List < SearchResultGroup > ( ) ; // Overall search results
241
244
242
- if ( prevFieldModifiers != fieldModifiers || prevPropertyModifiers != propertyModifiers )
245
+ if ( prevFieldModifiers != fieldModifiers || prevPropertyModifiers != propertyModifiers || prevSearchSerializableVariablesOnly != searchSerializableVariablesOnly )
243
246
typeToVariables . Clear ( ) ;
244
247
245
248
searchedObjects . Clear ( ) ;
249
+ animationClipUniqueBindings . Clear ( ) ;
246
250
callStack . Clear ( ) ;
247
251
objectsToSearchSet . Clear ( ) ;
248
252
sceneObjectsToSearchSet . Clear ( ) ;
@@ -265,6 +269,8 @@ public SearchResult Run( Parameters searchParameters )
265
269
foreach ( var cacheEntry in assetDependencyCache . Values )
266
270
cacheEntry . searchResult = CacheEntry . Result . Unknown ;
267
271
272
+ lastRefreshedCacheEntry = null ;
273
+
268
274
if ( typeToSearchFunction == null )
269
275
{
270
276
typeToSearchFunction = new Dictionary < Type , Func < Object , ReferenceNode > > ( )
@@ -287,6 +293,7 @@ public SearchResult Run( Parameters searchParameters )
287
293
288
294
prevFieldModifiers = fieldModifiers ;
289
295
prevPropertyModifiers = propertyModifiers ;
296
+ prevSearchSerializableVariablesOnly = searchSerializableVariablesOnly ;
290
297
291
298
searchPrefabConnections = false ;
292
299
searchMonoBehavioursForScript = false ;
@@ -428,9 +435,6 @@ public SearchResult Run( Parameters searchParameters )
428
435
} ) ;
429
436
}
430
437
431
- // By default, search only serializable variables for references
432
- searchSerializableVariablesOnly = ! searchParameters . searchNonSerializableVariables ;
433
-
434
438
// Initialize the nodes of searched asset(s)
435
439
foreach ( Object obj in objectsToSearchSet )
436
440
searchedObjects . Add ( obj . Hash ( ) , PopReferenceNode ( obj ) ) ;
@@ -539,7 +543,7 @@ public SearchResult Run( Parameters searchParameters )
539
543
searchResult . Add ( currentSearchResultGroup ) ;
540
544
}
541
545
542
- // Search non-serializable variables for references only if we are currently searching a scene and the editor is in play mode
546
+ // Search non-serializable variables for references while searching a scene in play mode
543
547
if ( isInPlayMode )
544
548
searchSerializableVariablesOnly = false ;
545
549
@@ -626,28 +630,39 @@ public SearchResult Run( Parameters searchParameters )
626
630
}
627
631
catch ( Exception e )
628
632
{
629
- Debug . LogException ( e ) ;
630
-
633
+ StringBuilder sb = new StringBuilder ( objectsToSearchSet . Count * 50 + callStack . Count * 50 + 500 ) ;
634
+ sb . AppendLine ( "<b>AssetUsageDetector Error:</b>" ) . AppendLine ( ) ;
631
635
if ( callStack . Count > 0 )
632
636
{
633
- StringBuilder sb = new StringBuilder ( callStack . Count * 50 ) ;
634
637
sb . AppendLine ( "Stack contents: " ) ;
635
- for ( int i = 0 ; i < callStack . Count ; i ++ )
638
+ for ( int i = callStack . Count - 1 ; i >= 0 ; i -- )
636
639
{
637
640
sb . Append ( i ) . Append ( ": " ) ;
638
641
639
642
Object unityObject = callStack [ i ] as Object ;
640
- if ( unityObject != null )
643
+ if ( unityObject )
641
644
sb . Append ( unityObject . name ) . Append ( " (" ) . Append ( unityObject . GetType ( ) ) . AppendLine ( ")" ) ;
642
645
else if ( callStack [ i ] != null )
643
646
sb . Append ( callStack [ i ] . GetType ( ) ) . AppendLine ( " object" ) ;
644
647
else
645
648
sb . AppendLine ( "<<destroyed>>" ) ;
646
649
}
647
650
648
- Debug . LogError ( sb . ToString ( ) ) ;
651
+ sb . AppendLine ( ) ;
652
+ }
653
+
654
+ sb . AppendLine ( "Searching references of: " ) ;
655
+ foreach ( Object obj in objectsToSearchSet )
656
+ {
657
+ if ( obj )
658
+ sb . Append ( obj . name ) . Append ( " (" ) . Append ( obj . GetType ( ) ) . AppendLine ( ")" ) ;
649
659
}
650
660
661
+ sb . AppendLine ( ) ;
662
+ sb . Append ( e ) . AppendLine ( ) ;
663
+
664
+ Debug . LogError ( sb . ToString ( ) ) ;
665
+
651
666
try
652
667
{
653
668
InitializeSearchResultNodes ( searchResult ) ;
@@ -1012,11 +1027,17 @@ private ReferenceNode SearchComponent( Object unityObject )
1012
1027
// If this component is an Animation, search its animation clips for references
1013
1028
foreach ( AnimationState anim in ( Animation ) component )
1014
1029
referenceNode . AddLinkTo ( SearchObject ( anim . clip ) ) ;
1030
+
1031
+ // Search the objects that are animated by this Animation component for references
1032
+ SearchAnimatedObjects ( referenceNode ) ;
1015
1033
}
1016
1034
else if ( component is Animator )
1017
1035
{
1018
- // If this component is an Animator, search its animation clips for references
1036
+ // If this component is an Animator, search its animation clips for references (via AnimatorController)
1019
1037
referenceNode . AddLinkTo ( SearchObject ( ( ( Animator ) component ) . runtimeAnimatorController ) ) ;
1038
+
1039
+ // Search the objects that are animated by this Animator component for references
1040
+ SearchAnimatedObjects ( referenceNode ) ;
1020
1041
}
1021
1042
#if UNITY_2017_2_OR_NEWER
1022
1043
else if ( component is Tilemap )
@@ -1227,6 +1248,85 @@ private ReferenceNode SearchSpriteAtlas( Object unityObject )
1227
1248
}
1228
1249
#endif
1229
1250
1251
+ // Find references from an Animation/Animator component to the objects that it animates
1252
+ private void SearchAnimatedObjects ( ReferenceNode referenceNode )
1253
+ {
1254
+ GameObject root = ( ( Component ) referenceNode . nodeObject ) . gameObject ;
1255
+ AnimationClip [ ] clips = AnimationUtility . GetAnimationClips ( root ) ;
1256
+ for ( int i = 0 ; i < clips . Length ; i ++ )
1257
+ {
1258
+ AnimationClip clip = clips [ i ] ;
1259
+ bool isClipUnique = true ;
1260
+ for ( int j = i - 1 ; j >= 0 ; j -- )
1261
+ {
1262
+ if ( clips [ j ] == clip )
1263
+ {
1264
+ isClipUnique = false ;
1265
+ break ;
1266
+ }
1267
+ }
1268
+
1269
+ if ( ! isClipUnique )
1270
+ continue ;
1271
+
1272
+ EditorCurveBinding [ ] uniqueBindings ;
1273
+ if ( ! animationClipUniqueBindings . TryGetValue ( clip , out uniqueBindings ) )
1274
+ {
1275
+ // Calculate all the "unique" paths that the animation clip's curves have
1276
+ // Both float curves (GetCurveBindings) and object reference curves (GetObjectReferenceCurveBindings) are checked
1277
+ List < EditorCurveBinding > _uniqueBindings = new List < EditorCurveBinding > ( 2 ) ;
1278
+ EditorCurveBinding [ ] bindings = AnimationUtility . GetCurveBindings ( clip ) ;
1279
+ for ( int j = 0 ; j < bindings . Length ; j ++ )
1280
+ {
1281
+ string bindingPath = bindings [ j ] . path ;
1282
+ if ( string . IsNullOrEmpty ( bindingPath ) ) // Ignore the root animated object
1283
+ continue ;
1284
+
1285
+ bool isBindingUnique = true ;
1286
+ for ( int k = _uniqueBindings . Count - 1 ; k >= 0 ; k -- )
1287
+ {
1288
+ if ( bindingPath == _uniqueBindings [ k ] . path )
1289
+ {
1290
+ isBindingUnique = false ;
1291
+ break ;
1292
+ }
1293
+ }
1294
+
1295
+ if ( isBindingUnique )
1296
+ _uniqueBindings . Add ( bindings [ j ] ) ;
1297
+ }
1298
+
1299
+ bindings = AnimationUtility . GetObjectReferenceCurveBindings ( clip ) ;
1300
+ for ( int j = 0 ; j < bindings . Length ; j ++ )
1301
+ {
1302
+ string bindingPath = bindings [ j ] . path ;
1303
+ if ( string . IsNullOrEmpty ( bindingPath ) ) // Ignore the root animated object
1304
+ continue ;
1305
+
1306
+ bool isBindingUnique = true ;
1307
+ for ( int k = _uniqueBindings . Count - 1 ; k >= 0 ; k -- )
1308
+ {
1309
+ if ( bindingPath == _uniqueBindings [ k ] . path )
1310
+ {
1311
+ isBindingUnique = false ;
1312
+ break ;
1313
+ }
1314
+ }
1315
+
1316
+ if ( isBindingUnique )
1317
+ _uniqueBindings . Add ( bindings [ j ] ) ;
1318
+ }
1319
+
1320
+ uniqueBindings = _uniqueBindings . ToArray ( ) ;
1321
+ animationClipUniqueBindings [ clip ] = uniqueBindings ;
1322
+ }
1323
+
1324
+ string clipName = clip . name ;
1325
+ for ( int j = 0 ; j < uniqueBindings . Length ; j ++ )
1326
+ referenceNode . AddLinkTo ( SearchObject ( AnimationUtility . GetAnimatedObject ( root , uniqueBindings [ j ] ) ) , "Animated via clip: " + clipName ) ;
1327
+ }
1328
+ }
1329
+
1230
1330
// Search through field and properties of an object for references with SerializedObject
1231
1331
private void SearchWithSerializedObject ( ReferenceNode referenceNode )
1232
1332
{
@@ -1319,12 +1419,10 @@ private void SearchFieldsAndPropertiesOf( ReferenceNode referenceNode )
1319
1419
}
1320
1420
}
1321
1421
}
1322
- catch ( UnassignedReferenceException )
1323
- { }
1324
- catch ( MissingReferenceException )
1325
- { }
1326
- catch ( MissingComponentException )
1327
- { }
1422
+ catch ( UnassignedReferenceException ) { }
1423
+ catch ( MissingReferenceException ) { }
1424
+ catch ( MissingComponentException ) { }
1425
+ catch ( NotImplementedException ) { }
1328
1426
}
1329
1427
}
1330
1428
@@ -1341,7 +1439,7 @@ private VariableGetterHolder[] GetFilteredVariablesForType( Type type )
1341
1439
// 2- skip primitive types, enums and strings
1342
1440
// 3- skip common Unity types that can't hold any references (e.g. Vector3, Rect, Color, Quaternion)
1343
1441
//
1344
- // P.S. IsPrimitiveUnityType () extension function handles steps 2) and 3)
1442
+ // P.S. IsIgnoredUnityType () extension function handles steps 2) and 3)
1345
1443
1346
1444
validVariables . Clear ( ) ;
1347
1445
@@ -1361,16 +1459,7 @@ private VariableGetterHolder[] GetFilteredVariablesForType( Type type )
1361
1459
continue ;
1362
1460
1363
1461
// Skip primitive types
1364
- Type variableType = field . FieldType ;
1365
- if ( variableType . IsPrimitiveUnityType ( ) )
1366
- continue ;
1367
-
1368
- // Searching assembly variables for reference throws InvalidCastException on .NET 4.0 runtime
1369
- if ( typeof ( Type ) . IsAssignableFrom ( variableType ) || variableType . Namespace == reflectionNameSpace )
1370
- continue ;
1371
-
1372
- // Searching pointer variables for reference throws ArgumentException
1373
- if ( variableType . IsPointer )
1462
+ if ( field . FieldType . IsIgnoredUnityType ( ) )
1374
1463
continue ;
1375
1464
1376
1465
// Additional filtering for fields:
@@ -1382,7 +1471,7 @@ private VariableGetterHolder[] GetFilteredVariablesForType( Type type )
1382
1471
1383
1472
VariableGetVal getter = field . CreateGetter ( type ) ;
1384
1473
if ( getter != null )
1385
- validVariables . Add ( new VariableGetterHolder ( field , getter , field . IsSerializable ( ) ) ) ;
1474
+ validVariables . Add ( new VariableGetterHolder ( field , getter , searchSerializableVariablesOnly ? field . IsSerializable ( ) : true ) ) ;
1386
1475
}
1387
1476
1388
1477
currType = currType . BaseType ;
@@ -1404,16 +1493,7 @@ private VariableGetterHolder[] GetFilteredVariablesForType( Type type )
1404
1493
continue ;
1405
1494
1406
1495
// Skip primitive types
1407
- Type variableType = property . PropertyType ;
1408
- if ( variableType . IsPrimitiveUnityType ( ) )
1409
- continue ;
1410
-
1411
- // Searching assembly variables for reference throws InvalidCastException on .NET 4.0 runtime
1412
- if ( typeof ( Type ) . IsAssignableFrom ( variableType ) || variableType . Namespace == reflectionNameSpace )
1413
- continue ;
1414
-
1415
- // Searching pointer variables for reference throws ArgumentException
1416
- if ( variableType . IsPointer )
1496
+ if ( property . PropertyType . IsIgnoredUnityType ( ) )
1417
1497
continue ;
1418
1498
1419
1499
// Skip properties without a getter function
@@ -1458,7 +1538,7 @@ private VariableGetterHolder[] GetFilteredVariablesForType( Type type )
1458
1538
{
1459
1539
VariableGetVal getter = property . CreateGetter ( ) ;
1460
1540
if ( getter != null )
1461
- validVariables . Add ( new VariableGetterHolder ( property , getter , property . IsSerializable ( ) ) ) ;
1541
+ validVariables . Add ( new VariableGetterHolder ( property , getter , searchSerializableVariablesOnly ? property . IsSerializable ( ) : true ) ) ;
1462
1542
}
1463
1543
}
1464
1544
@@ -1506,8 +1586,55 @@ private bool AssetHasAnyReference( string assetPath )
1506
1586
FileInfo assetFile = new FileInfo ( dependencies [ i ] ) ;
1507
1587
if ( ! assetFile . Exists || assetFile . Length != fileSizes [ i ] )
1508
1588
{
1589
+ // Although not reproduced, it is reported that this section caused StackOverflowException due to infinite loop,
1590
+ // if that happens, log useful information to help reproduce the issue
1591
+ if ( lastRefreshedCacheEntry == cacheEntry )
1592
+ {
1593
+ StringBuilder sb = new StringBuilder ( 1000 ) ;
1594
+ sb . AppendLine ( "<b>Infinite loop while refreshing a cache entry, please report it to the author.</b>" ) . AppendLine ( ) ;
1595
+ sb . Append ( "Asset path: " ) . AppendLine ( assetPath ) ;
1596
+
1597
+ for ( int j = 0 ; j < 2 ; j ++ )
1598
+ {
1599
+ if ( j == 1 )
1600
+ {
1601
+ cacheEntry . Refresh ( assetPath ) ;
1602
+ dependencies = cacheEntry . dependencies ;
1603
+ fileSizes = cacheEntry . fileSizes ;
1604
+ }
1605
+
1606
+ sb . AppendLine ( ) . AppendLine ( j == 0 ? "Old Dependencies:" : "New Dependencies" ) ;
1607
+ for ( int k = 0 ; k < dependencies . Length ; k ++ )
1608
+ {
1609
+ sb . Append ( "- " ) . Append ( dependencies [ k ] ) ;
1610
+
1611
+ if ( Directory . Exists ( dependencies [ k ] ) )
1612
+ {
1613
+ sb . Append ( " (Dir)" ) ;
1614
+ if ( fileSizes [ k ] != 0L )
1615
+ sb . Append ( " WasCachedAsFile: " ) . Append ( fileSizes [ k ] ) ;
1616
+ }
1617
+ else
1618
+ {
1619
+ assetFile = new FileInfo ( dependencies [ k ] ) ;
1620
+ sb . Append ( " (File) " ) . Append ( "CachedSize: " ) . Append ( fileSizes [ k ] ) ;
1621
+ if ( assetFile . Exists )
1622
+ sb . Append ( " RealSize: " ) . Append ( assetFile . Length ) ;
1623
+ else
1624
+ sb . Append ( " NoLongerExists" ) ;
1625
+ }
1626
+
1627
+ sb . AppendLine ( ) ;
1628
+ }
1629
+ }
1630
+
1631
+ Debug . LogError ( sb . ToString ( ) ) ;
1632
+ return false ;
1633
+ }
1634
+
1509
1635
cacheEntry . Refresh ( assetPath ) ;
1510
1636
cacheEntry . searchResult = CacheEntry . Result . Unknown ;
1637
+ lastRefreshedCacheEntry = cacheEntry ;
1511
1638
1512
1639
return AssetHasAnyReference ( assetPath ) ;
1513
1640
}
@@ -1659,7 +1786,7 @@ private void LoadCache()
1659
1786
catch ( Exception e )
1660
1787
{
1661
1788
assetDependencyCache = null ;
1662
- Debug . LogException ( e ) ;
1789
+ Debug . LogWarning ( "Couldn't load cache (probably cache format has changed in an update), will regenerate cache. \n " + e . ToString ( ) ) ;
1663
1790
}
1664
1791
}
1665
1792
}
0 commit comments