Skip to content

Commit

Permalink
- Previous HideInInspector solution wasn't working if a nested variab…
Browse files Browse the repository at this point in the history
…le had HideInInspector, replaced it with a better solution

- Added SerializeReference support
- Added 2020.1 generic types support
- Fixed deprecated warning messages on 2020.1
  • Loading branch information
yasirkula committed Jul 27, 2020
1 parent 6280305 commit 68f1b28
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 53 deletions.
47 changes: 42 additions & 5 deletions Plugins/AssetUsageDetector/Editor/AssetUsageDetector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
using System.IO;
using System.Text;
using Object = UnityEngine.Object;
#if UNITY_2018_3_OR_NEWER
using PrefabStage = UnityEditor.Experimental.SceneManagement.PrefabStage;
using PrefabStageUtility = UnityEditor.Experimental.SceneManagement.PrefabStageUtility;
#endif

namespace AssetUsageDetectorNamespace
{
Expand Down Expand Up @@ -79,8 +83,11 @@ public class Parameters
private bool isInPlayMode;

#if UNITY_2018_3_OR_NEWER
private UnityEditor.Experimental.SceneManagement.PrefabStage openPrefabStage;
private PrefabStage openPrefabStage;
private GameObject openPrefabStagePrefabAsset;
#if UNITY_2020_1_OR_NEWER
private GameObject openPrefabStageContextObject;
#endif
#endif

private int searchedObjectsCount; // Number of searched objects
Expand Down Expand Up @@ -116,7 +123,7 @@ public SearchResult Run( Parameters searchParameters )
#if UNITY_2018_3_OR_NEWER
openPrefabStagePrefabAsset = null;
string openPrefabStageAssetPath = null;
openPrefabStage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
openPrefabStage = PrefabStageUtility.GetCurrentPrefabStage();
if( openPrefabStage != null )
{
if( !openPrefabStage.stageHandle.IsValid() )
Expand All @@ -130,8 +137,17 @@ public SearchResult Run( Parameters searchParameters )
return new SearchResult( false, null, null, this, searchParameters );
}

openPrefabStagePrefabAsset = AssetDatabase.LoadAssetAtPath<GameObject>( openPrefabStage.prefabAssetPath );
openPrefabStageAssetPath = openPrefabStage.prefabAssetPath;
#if UNITY_2020_1_OR_NEWER
string prefabAssetPath = openPrefabStage.assetPath;
#else
string prefabAssetPath = openPrefabStage.prefabAssetPath;
#endif
openPrefabStagePrefabAsset = AssetDatabase.LoadAssetAtPath<GameObject>( prefabAssetPath );
openPrefabStageAssetPath = prefabAssetPath;

#if UNITY_2020_1_OR_NEWER
openPrefabStageContextObject = openPrefabStage.openedFromInstanceRoot;
#endif
}
}
#endif
Expand Down Expand Up @@ -583,7 +599,28 @@ public SearchResult Run( Parameters searchParameters )
#if UNITY_2018_3_OR_NEWER
// If a prefab stage was open when the search was triggered, try reopening the prefab stage after the search is completed
if( !string.IsNullOrEmpty( openPrefabStageAssetPath ) )
AssetDatabase.OpenAsset( AssetDatabase.LoadAssetAtPath<GameObject>( openPrefabStageAssetPath ) );
{
#if UNITY_2020_1_OR_NEWER
bool shouldOpenPrefabStageWithoutContext = true;
if( openPrefabStageContextObject != null && !openPrefabStageContextObject.Equals( null ) )
{
try
{
// Try to access this method: https://github.com/Unity-Technologies/UnityCsReference/blob/73925b1711847c067e607ec8371f8e9ffe7ab65d/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStageUtility.cs#L61-L65
MethodInfo prefabStageOpenerWithContext = typeof( PrefabStageUtility ).GetMethod( "OpenPrefab", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, null, new Type[2] { typeof( string ), typeof( GameObject ) }, null );
if( prefabStageOpenerWithContext != null )
{
prefabStageOpenerWithContext.Invoke( null, new object[2] { openPrefabStageAssetPath, openPrefabStageContextObject } );
shouldOpenPrefabStageWithoutContext = false;
}
}
catch { }
}

if( shouldOpenPrefabStageWithoutContext )
#endif
AssetDatabase.OpenAsset( AssetDatabase.LoadAssetAtPath<GameObject>( openPrefabStageAssetPath ) );
}
#endif
}
}
Expand Down
152 changes: 116 additions & 36 deletions Plugins/AssetUsageDetector/Editor/AssetUsageDetectorSearchFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,6 @@ private static string ExtractGUIDFromString( string str )
private readonly Dictionary<Type, VariableGetterHolder[]> typeToVariables = new Dictionary<Type, VariableGetterHolder[]>( 4096 );
private readonly List<VariableGetterHolder> validVariables = new List<VariableGetterHolder>( 32 );

// Dictionary to store whether or not instances of a Type can be searched with SearchVariablesWithSerializedObject
private readonly Dictionary<Type, bool> typesSearchabilityWithSerializedObject = new Dictionary<Type, bool>( 4096 );

// Path(s) of .cginc, .cg, .hlsl and .glslinc assets in assetsToSearchSet
private readonly HashSet<string> shaderIncludesToSearchSet = new HashSet<string>();

Expand All @@ -194,6 +191,10 @@ private static string ExtractGUIDFromString( string str )
private BindingFlags fieldModifiers, propertyModifiers;
private BindingFlags prevFieldModifiers, prevPropertyModifiers;

// Unity's internal function that returns a SerializedProperty's corresponding FieldInfo
private delegate FieldInfo FieldInfoGetter( SerializedProperty p, out Type t );
private FieldInfoGetter fieldInfoGetter;

private void InitializeSearchFunctionsData( Parameters searchParameters )
{
if( typeToSearchFunction == null )
Expand Down Expand Up @@ -330,6 +331,13 @@ private void InitializeSearchFunctionsData( Parameters searchParameters )
alwaysSearchedExtensionsSet.Add( "shadersubgraph" );
}
#endif

#if UNITY_2019_3_OR_NEWER
MethodInfo fieldInfoGetterMethod = typeof( Editor ).Assembly.GetType( "UnityEditor.ScriptAttributeUtility" ).GetMethod( "GetFieldInfoAndStaticTypeFromProperty", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static );
#else
MethodInfo fieldInfoGetterMethod = typeof( Editor ).Assembly.GetType( "UnityEditor.ScriptAttributeUtility" ).GetMethod( "GetFieldInfoFromProperty", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static );
#endif
fieldInfoGetter = (FieldInfoGetter) Delegate.CreateDelegate( typeof( FieldInfoGetter ), fieldInfoGetterMethod );
}

private ReferenceNode SearchGameObject( Object unityObject )
Expand Down Expand Up @@ -967,29 +975,33 @@ private void SearchVariablesWithSerializedObject( ReferenceNode referenceNode )
{
if( !isInPlayMode || referenceNode.nodeObject.IsAsset() )
{
// Some Types can be searched with SearchVariablesWithReflection only
Type nodeObjectType = referenceNode.nodeObject.GetType();
bool typeSearchability;
if( !typesSearchabilityWithSerializedObject.TryGetValue( nodeObjectType, out typeSearchability ) )
{
// typesSearchabilityWithSerializedObject is updated when GetFilteredVariablesForType is called
GetFilteredVariablesForType( nodeObjectType );
typeSearchability = typesSearchabilityWithSerializedObject[nodeObjectType];
}

if( !typeSearchability )
{
SearchVariablesWithReflection( referenceNode );
return;
}

SerializedObject so = new SerializedObject( (Object) referenceNode.nodeObject );
SerializedProperty iterator = so.GetIterator();
if( iterator.NextVisible( true ) )
SerializedProperty iteratorVisible = so.GetIterator();
if( iterator.Next( true ) )
{
bool iteratingVisible = iteratorVisible.NextVisible( true );
bool enterChildren;
do
{
// Iterate over NextVisible properties AND the properties that have corresponding FieldInfos (internal Unity
// properties don't have FieldInfos so we are skipping them, which is good because search results found in
// those properties aren't interesting and mostly confusing)
bool isVisible = iteratingVisible && SerializedProperty.EqualContents( iterator, iteratorVisible );
if( isVisible )
iteratingVisible = iteratorVisible.NextVisible( true );
else
{
Type propFieldType;
isVisible = iterator.type == "Array" || fieldInfoGetter( iterator, out propFieldType ) != null;
}

if( !isVisible )
{
enterChildren = false;
continue;
}

ReferenceNode searchResult;
switch( iterator.propertyType )
{
Expand All @@ -1001,6 +1013,12 @@ private void SearchVariablesWithSerializedObject( ReferenceNode referenceNode )
searchResult = SearchObject( iterator.exposedReferenceValue );
enterChildren = false;
break;
#if UNITY_2019_3_OR_NEWER
case SerializedPropertyType.ManagedReference:
searchResult = SearchObject( GetRawSerializedPropertyValue( iterator ) );
enterChildren = false;
break;
#endif
case SerializedPropertyType.Generic:
searchResult = null;
enterChildren = true;
Expand All @@ -1019,7 +1037,7 @@ private void SearchVariablesWithSerializedObject( ReferenceNode referenceNode )
if( !propertyPath.EndsWithFast( "m_RD.texture" ) )
referenceNode.AddLinkTo( searchResult, "Variable: " + propertyPath );
}
} while( iterator.NextVisible( enterChildren ) );
} while( iterator.Next( enterChildren ) );

return;
}
Expand Down Expand Up @@ -1084,10 +1102,6 @@ private VariableGetterHolder[] GetFilteredVariablesForType( Type type )
if( typeToVariables.TryGetValue( type, out result ) )
return result;

// SearchVariablesWithSerializedObject function can't iterate over fields that have HideInInspector
// or SerializeReference attributes. Types that have such variables must be searched with reflection
bool searchabilityWithSerializedObject = true;

// This is the first time this type of object is seen, filter and cache its variables
// Variable filtering process:
// 1- skip Obsolete variables
Expand Down Expand Up @@ -1126,17 +1140,7 @@ private VariableGetterHolder[] GetFilteredVariablesForType( Type type )

VariableGetVal getter = field.CreateGetter( type );
if( getter != null )
{
validVariables.Add( new VariableGetterHolder( field, getter, searchSerializableVariablesOnly ? field.IsSerializable() : true ) );

if( searchabilityWithSerializedObject && Attribute.IsDefined( field, typeof( HideInInspector ) ) )
searchabilityWithSerializedObject = false;

#if UNITY_2019_3_OR_NEWER
if( searchabilityWithSerializedObject && Attribute.IsDefined( field, typeof( SerializeReference ) ) )
searchabilityWithSerializedObject = false;
#endif
}
}

currType = currType.BaseType;
Expand Down Expand Up @@ -1215,11 +1219,87 @@ private VariableGetterHolder[] GetFilteredVariablesForType( Type type )

// Cache the filtered fields
typeToVariables.Add( type, result );
typesSearchabilityWithSerializedObject.Add( type, searchabilityWithSerializedObject );

return result;
}

// Credit: http://answers.unity.com/answers/425602/view.html
// Returns the raw System.Object value of a SerializedProperty
public object GetRawSerializedPropertyValue( SerializedProperty property )
{
object result = property.serializedObject.targetObject;
string[] path = property.propertyPath.Replace( ".Array.data[", "[" ).Split( '.' );
for( int i = 0; i < path.Length; i++ )
{
string pathElement = path[i];

int arrayStartIndex = pathElement.IndexOf( '[' );
if( arrayStartIndex < 0 )
result = GetFieldValue( result, pathElement );
else
{
string variableName = pathElement.Substring( 0, arrayStartIndex );

int arrayEndIndex = pathElement.IndexOf( ']', arrayStartIndex + 1 );
int arrayElementIndex = int.Parse( pathElement.Substring( arrayStartIndex + 1, arrayEndIndex - arrayStartIndex - 1 ) );
result = GetFieldValue( result, variableName, arrayElementIndex );
}
}

return result;
}

// Credit: http://answers.unity.com/answers/425602/view.html
private object GetFieldValue( object source, string fieldName )
{
if( source == null )
return null;

FieldInfo fieldInfo = null;
Type type = source.GetType();
while( fieldInfo == null && type != typeof( object ) )
{
fieldInfo = type.GetField( fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly );
type = type.BaseType;
}

if( fieldInfo != null )
return fieldInfo.GetValue( source );

PropertyInfo propertyInfo = null;
type = source.GetType();
while( propertyInfo == null && type != typeof( object ) )
{
propertyInfo = type.GetProperty( fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.IgnoreCase );
type = type.BaseType;
}

if( propertyInfo != null )
return propertyInfo.GetValue( source, null );

if( fieldName.Length > 2 && fieldName.StartsWith( "m_", StringComparison.OrdinalIgnoreCase ) )
return GetFieldValue( source, fieldName.Substring( 2 ) );

return null;
}

// Credit: http://answers.unity.com/answers/425602/view.html
private object GetFieldValue( object source, string fieldName, int arrayIndex )
{
IEnumerable enumerable = GetFieldValue( source, fieldName ) as IEnumerable;
if( enumerable == null )
return null;

if( enumerable is IList )
return ( (IList) enumerable )[arrayIndex];

IEnumerator enumerator = enumerable.GetEnumerator();
for( int i = 0; i <= arrayIndex; i++ )
enumerator.MoveNext();

return enumerator.Current;
}

// Iterates over all occurrences of specific key-value pairs in string
// Example1: #include "VALUE" valuePrefix=#include, valueWrapperChar="
// Example2: "guid": "VALUE" valuePrefix="guid", valueWrapperChar="
Expand Down
20 changes: 14 additions & 6 deletions Plugins/AssetUsageDetector/Editor/AssetUsageDetectorWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
using System.Collections.Generic;
using System.Reflection;
using Object = UnityEngine.Object;
#if UNITY_2018_3_OR_NEWER
using PrefabStage = UnityEditor.Experimental.SceneManagement.PrefabStage;
using PrefabStageUtility = UnityEditor.Experimental.SceneManagement.PrefabStageUtility;
#endif

namespace AssetUsageDetectorNamespace
{
Expand Down Expand Up @@ -236,15 +240,15 @@ private void OnEnable()
mainWindow = this;

#if UNITY_2018_3_OR_NEWER
UnityEditor.Experimental.SceneManagement.PrefabStage.prefabStageClosing -= ReplacePrefabStageObjectsWithAssets;
UnityEditor.Experimental.SceneManagement.PrefabStage.prefabStageClosing += ReplacePrefabStageObjectsWithAssets;
PrefabStage.prefabStageClosing -= ReplacePrefabStageObjectsWithAssets;
PrefabStage.prefabStageClosing += ReplacePrefabStageObjectsWithAssets;
#endif
}

private void OnDisable()
{
#if UNITY_2018_3_OR_NEWER
UnityEditor.Experimental.SceneManagement.PrefabStage.prefabStageClosing -= ReplacePrefabStageObjectsWithAssets;
PrefabStage.prefabStageClosing -= ReplacePrefabStageObjectsWithAssets;
#endif

if( mainWindow == this )
Expand Down Expand Up @@ -647,7 +651,7 @@ private void InitiateSearch()
SavePrefs();

#if UNITY_2018_3_OR_NEWER
ReplacePrefabStageObjectsWithAssets( UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage() );
ReplacePrefabStageObjectsWithAssets( PrefabStageUtility.GetCurrentPrefabStage() );
#endif

// Start searching
Expand Down Expand Up @@ -676,13 +680,17 @@ private void InitiateSearch()

#if UNITY_2018_3_OR_NEWER
// Try replacing searched objects who are part of currently open prefab stage with their corresponding prefab assets
public void ReplacePrefabStageObjectsWithAssets( UnityEditor.Experimental.SceneManagement.PrefabStage prefabStage )
public void ReplacePrefabStageObjectsWithAssets( PrefabStage prefabStage )
{
if( prefabStage == null || !prefabStage.stageHandle.IsValid() )
return;

#if UNITY_2020_1_OR_NEWER
GameObject prefabAsset = AssetDatabase.LoadAssetAtPath<GameObject>( prefabStage.assetPath );
#else
GameObject prefabAsset = AssetDatabase.LoadAssetAtPath<GameObject>( prefabStage.prefabAssetPath );
if( prefabAsset == null )
#endif
if( prefabAsset == null || prefabAsset.Equals( null ) )
return;

for( int i = 0; i < objectsToSearch.Count; i++ )
Expand Down
Loading

0 comments on commit 68f1b28

Please sign in to comment.