You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
CrowdControl/Assets/Plugins/AssetUsageDetector/Editor/SearchResult.cs

1395 lines
50 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;
namespace AssetUsageDetectorNamespace
{
// Custom class to hold search results
[Serializable]
public class SearchResult : IEnumerable<SearchResultGroup>, ISerializationCallbackReceiver
{
[Serializable]
internal class SerializableResultGroup
{
public string title;
public SearchResultGroup.GroupType type;
public bool isExpanded;
public bool pendingSearch;
public SearchResultTreeViewState treeViewState;
public List<int> initialSerializedNodes;
}
[Serializable]
internal class SerializableNode
{
[Serializable]
public class SerializableLinkDescriptions
{
public List<string> value;
}
public string label;
public int instanceId;
public bool isUnityObject, isMainReference;
public ReferenceNode.UsedState usedState;
public List<int> links;
public List<SerializableLinkDescriptions> linkDescriptions;
public List<bool> linkWeakStates;
}
internal class SortedEntry : IComparable<SortedEntry>
{
public readonly string assetPath, subAssetName;
public readonly bool isMainAsset;
public readonly Transform transform;
public readonly object entry;
public SortedEntry( ReferenceNode node ) : this( node.nodeObject as Object )
{
entry = node;
}
public SortedEntry( ReferenceNode.Link link ) : this( link.targetNode.nodeObject as Object )
{
entry = link;
}
private SortedEntry( Object obj )
{
if( obj )
{
assetPath = AssetDatabase.GetAssetPath( obj );
if( string.IsNullOrEmpty( assetPath ) )
{
assetPath = null;
if( obj is Component )
transform = ( (Component) obj ).transform;
else if( obj is GameObject )
transform = ( (GameObject) obj ).transform;
}
else
{
isMainAsset = AssetDatabase.IsMainAsset( ( obj is Component ) ? ( (Component) obj ).gameObject : obj );
if( !isMainAsset )
subAssetName = obj.name;
}
}
}
// Sorting order:
// 1) Scene objects come first and are sorted by their absolute sibling indices in Hierarchy
// 2) Assets come later and are sorted by their asset paths (for assets sharing the same path, main assets come first and sub-assets are then sorted by their names)
// 3) Regular C# objects come last
int IComparable<SortedEntry>.CompareTo( SortedEntry other )
{
if( this == other )
return 0;
else if( assetPath == null )
{
if( !transform )
return 1;
else if( other.assetPath == null )
return other.transform ? Utilities.CompareHierarchySiblingIndices( transform, other.transform ) : -1;
else
return -1;
}
else if( other.assetPath == null )
return other.transform ? 1 : -1;
else
{
int assetPathComparison = EditorUtility.NaturalCompare( assetPath, other.assetPath );
if( assetPathComparison != 0 )
return assetPathComparison;
else if( isMainAsset )
return -1;
else if( other.isMainAsset )
return 1;
else
return subAssetName.CompareTo( other.subAssetName );
}
}
}
private bool success; // This isn't readonly so that it can be serialized
private List<SearchResultGroup> result;
private SceneSetup[] initialSceneSetup;
private AssetUsageDetector searchHandler;
private AssetUsageDetector.Parameters m_searchParameters;
// Each TreeView in the drawn search results must use unique ids for their TreeViewItems. Otherwise, strange things happen: https://forum.unity.com/threads/multiple-editor-treeviews-selection-issue.601471/
internal int nextTreeViewId = 1;
private List<SerializableNode> serializedNodes;
private List<SerializableResultGroup> serializedGroups;
private Object[] serializedUsedObjects;
public int NumberOfGroups { get { return result.Count; } }
public SearchResultGroup this[int index] { get { return result[index]; } }
public HashSet<Object> UsedObjects { get; private set; }
public bool SearchCompletedSuccessfully { get { return success; } }
public bool InitialSceneSetupConfigured { get { return initialSceneSetup != null && initialSceneSetup.Length > 0; } }
public bool HasPendingLazySceneSearchResults { get { return result.Find( ( group ) => group.PendingSearch ) != null; } }
public AssetUsageDetector.Parameters SearchParameters { get { return m_searchParameters; } }
public SearchResult( bool success, List<SearchResultGroup> result, HashSet<Object> usedObjects, SceneSetup[] initialSceneSetup, AssetUsageDetector searchHandler, AssetUsageDetector.Parameters searchParameters )
{
if( result == null )
result = new List<SearchResultGroup>( 0 );
this.success = success;
this.result = result;
this.UsedObjects = usedObjects ?? new HashSet<Object>();
this.initialSceneSetup = initialSceneSetup;
this.searchHandler = searchHandler;
this.m_searchParameters = searchParameters;
}
public void RemoveSearchResultGroup( SearchResultGroup searchResultGroup )
{
result.Remove( searchResultGroup );
}
public void RefreshSearchResultGroup( SearchResultGroup searchResultGroup, bool noAssetDatabaseChanges )
{
if( searchResultGroup == null )
{
Debug.LogError( "SearchResultGroup is null!" );
return;
}
int searchResultGroupIndex = result.IndexOf( searchResultGroup );
if( searchResultGroupIndex < 0 )
{
Debug.LogError( "SearchResultGroup is not a part of SearchResult!" );
return;
}
if( searchResultGroup.Type == SearchResultGroup.GroupType.Scene && EditorApplication.isPlaying && !EditorSceneManager.GetSceneByPath( searchResultGroup.ScenePath ).isLoaded )
{
Debug.LogError( "Can't search unloaded scene while in Play Mode!" );
return;
}
if( searchHandler == null )
searchHandler = new AssetUsageDetector();
SceneSearchMode searchInScenes = m_searchParameters.searchInScenes;
Object[] searchInScenesSubset = m_searchParameters.searchInScenesSubset;
bool searchInAssetsFolder = m_searchParameters.searchInAssetsFolder;
Object[] searchInAssetsSubset = m_searchParameters.searchInAssetsSubset;
bool searchInProjectSettings = m_searchParameters.searchInProjectSettings;
bool lazySceneSearch = m_searchParameters.lazySceneSearch;
bool calculateUnusedObjects = m_searchParameters.calculateUnusedObjects;
bool _noAssetDatabaseChanges = m_searchParameters.noAssetDatabaseChanges;
try
{
if( searchResultGroup.Type == SearchResultGroup.GroupType.Assets )
{
m_searchParameters.searchInScenes = SceneSearchMode.None;
m_searchParameters.searchInScenesSubset = null;
m_searchParameters.searchInProjectSettings = false;
}
else if( searchResultGroup.Type == SearchResultGroup.GroupType.ProjectSettings )
{
m_searchParameters.searchInScenes = SceneSearchMode.None;
m_searchParameters.searchInScenesSubset = null;
m_searchParameters.searchInAssetsFolder = false;
m_searchParameters.searchInAssetsSubset = null;
m_searchParameters.searchInProjectSettings = true;
}
else if( searchResultGroup.Type == SearchResultGroup.GroupType.Scene )
{
m_searchParameters.searchInScenes = SceneSearchMode.None;
m_searchParameters.searchInScenesSubset = new Object[1] { AssetDatabase.LoadAssetAtPath<SceneAsset>( searchResultGroup.ScenePath ) };
m_searchParameters.searchInAssetsFolder = false;
m_searchParameters.searchInAssetsSubset = null;
m_searchParameters.searchInProjectSettings = false;
}
else if( searchResultGroup.Type == SearchResultGroup.GroupType.DontDestroyOnLoad )
{
m_searchParameters.searchInScenes = (SceneSearchMode) 1024; // A unique value to search only the DontDestroyOnLoad scene
m_searchParameters.searchInScenesSubset = null;
m_searchParameters.searchInAssetsFolder = false;
m_searchParameters.searchInAssetsSubset = null;
m_searchParameters.searchInProjectSettings = false;
}
else
{
Debug.LogError( "Can't refresh group: " + searchResultGroup.Type );
return;
}
m_searchParameters.lazySceneSearch = false;
m_searchParameters.calculateUnusedObjects = result.Find( ( group ) => group.Type == SearchResultGroup.GroupType.UnusedObjects ) != null;
m_searchParameters.noAssetDatabaseChanges = noAssetDatabaseChanges;
// Make sure the AssetDatabase is up-to-date
AssetDatabase.SaveAssets();
SearchResult searchResult = searchHandler.Run( m_searchParameters );
if( !searchResult.success )
{
EditorUtility.DisplayDialog( "Error", "Couldn't refresh, check console for more info.", "OK" );
return;
}
if( searchResult.result != null )
{
SearchResultGroup newSearchResultGroup = searchResult.result.Find( ( group ) => group.Title == searchResultGroup.Title );
if( newSearchResultGroup != null )
result[searchResultGroupIndex] = newSearchResultGroup;
else
searchResultGroup.Clear();
UsedObjects.UnionWith( searchResult.UsedObjects );
SearchResultGroup unusedObjectsSearchResultGroup = result.Find( ( group ) => group.Type == SearchResultGroup.GroupType.UnusedObjects );
if( unusedObjectsSearchResultGroup != null )
{
SearchResultGroup newUnusedObjectsSearchResultGroup = searchResult.result.Find( ( group ) => group.Type == SearchResultGroup.GroupType.UnusedObjects );
if( newUnusedObjectsSearchResultGroup == null )
{
// UnusedObjects search result group doesn't exist in 2 cases:
// - When there are no search results found (NumberOfGroups == 0)
// - When all searched objects are referenced (NumberOfGroups > 0)
if( searchResult.result.Count > 0 )
unusedObjectsSearchResultGroup.Clear();
}
else
{
// NOTE: We can process UnusedObjects graphs iteratively (instead of recursively) because for the time being, these graphs have a maximum depth of 1
bool unusedObjectsGraphChanged = false;
HashSet<Object> newUnusedObjectsSet = new HashSet<Object>();
for( int i = newUnusedObjectsSearchResultGroup.NumberOfReferences - 1; i >= 0; i-- )
{
ReferenceNode node = newUnusedObjectsSearchResultGroup[i];
newUnusedObjectsSet.Add( node.UnityObject );
for( int j = node.NumberOfOutgoingLinks - 1; j >= 0; j-- )
newUnusedObjectsSet.Add( node[j].targetNode.UnityObject );
}
for( int i = unusedObjectsSearchResultGroup.NumberOfReferences - 1; i >= 0; i-- )
{
ReferenceNode node = unusedObjectsSearchResultGroup[i];
bool parentNodeRemoved = false;
Object obj = node.UnityObject;
if( !obj || !newUnusedObjectsSet.Contains( obj ) )
{
unusedObjectsSearchResultGroup.RemoveReference( i );
unusedObjectsGraphChanged = parentNodeRemoved = true;
}
bool hasUnusedSubObjects = false, hasUsedSubObjects = false;
bool hadSubObjects = node.NumberOfOutgoingLinks > 0;
for( int j = 0; j < node.NumberOfOutgoingLinks; j++ )
{
if( node[j].targetNode.usedState == ReferenceNode.UsedState.Used ) // User has explicitly displayed this used child object/sub-asset in the TreeView
continue;
Object _obj = node[j].targetNode.UnityObject;
if( newUnusedObjectsSet.Contains( _obj ) )
{
hasUnusedSubObjects = true;
if( parentNodeRemoved )
unusedObjectsSearchResultGroup.AddReference( node[j].targetNode );
}
else if( !parentNodeRemoved )
{
node.RemoveLink( j-- );
unusedObjectsGraphChanged = hasUsedSubObjects = true;
}
}
if( !parentNodeRemoved )
{
// When all sub-assets of a main asset are used, consider the main asset as used, as well
if( !hasUnusedSubObjects && hadSubObjects && AssetDatabase.IsMainAsset( obj ) )
{
unusedObjectsSearchResultGroup.RemoveReference( i );
unusedObjectsGraphChanged = true;
}
if( hasUsedSubObjects && node.usedState == ReferenceNode.UsedState.Unused )
node.usedState = ReferenceNode.UsedState.MixedCollapsed;
}
}
if( unusedObjectsGraphChanged && unusedObjectsSearchResultGroup.treeView != null )
{
unusedObjectsSearchResultGroup.treeView.SetSelection( new int[0], TreeViewSelectionOptions.FireSelectionChanged );
unusedObjectsSearchResultGroup.treeViewState.preSearchExpandedIds = null;
unusedObjectsSearchResultGroup.treeView.Reload();
unusedObjectsSearchResultGroup.treeView.ExpandAll();
}
}
if( unusedObjectsSearchResultGroup.NumberOfReferences == 0 )
result.Remove( unusedObjectsSearchResultGroup );
}
}
}
finally
{
m_searchParameters.searchInScenes = searchInScenes;
m_searchParameters.searchInScenesSubset = searchInScenesSubset;
m_searchParameters.searchInAssetsFolder = searchInAssetsFolder;
m_searchParameters.searchInAssetsSubset = searchInAssetsSubset;
m_searchParameters.searchInProjectSettings = searchInProjectSettings;
m_searchParameters.lazySceneSearch = lazySceneSearch;
m_searchParameters.calculateUnusedObjects = calculateUnusedObjects;
m_searchParameters.noAssetDatabaseChanges = _noAssetDatabaseChanges;
}
}
public float DrawOnGUI( EditorWindow window, float scrollPosition, bool noAssetDatabaseChanges )
{
for( int i = 0; i < result.Count; i++ )
{
scrollPosition = result[i].DrawOnGUI( this, window, scrollPosition, noAssetDatabaseChanges );
if( i < result.Count - 1 )
GUILayout.Space( 10f );
}
return scrollPosition;
}
public int IndexOf( SearchResultGroup searchResultGroup )
{
return result.IndexOf( searchResultGroup );
}
public void CollapseAllSearchResultGroups()
{
for( int i = 0; i < result.Count; i++ )
result[i].Collapse();
}
public void CancelDelayedTreeViewTooltip()
{
for( int i = 0; i < result.Count; i++ )
{
if( result[i].treeView != null )
result[i].treeView.CancelDelayedTooltip();
}
}
// Returns if RestoreInitialSceneSetup will have any effect on the current scene setup
public bool IsSceneSetupDifferentThanCurrentSetup()
{
if( initialSceneSetup == null )
return false;
SceneSetup[] sceneFinalSetup = EditorSceneManager.GetSceneManagerSetup();
if( initialSceneSetup.Length != sceneFinalSetup.Length )
return true;
for( int i = 0; i < sceneFinalSetup.Length; i++ )
{
bool sceneIsOneOfInitials = false;
for( int j = 0; j < initialSceneSetup.Length; j++ )
{
if( sceneFinalSetup[i].path == initialSceneSetup[j].path )
{
if( sceneFinalSetup[i].isLoaded != initialSceneSetup[j].isLoaded )
return true;
sceneIsOneOfInitials = true;
break;
}
}
if( !sceneIsOneOfInitials )
return true;
}
return false;
}
// Close the scenes that were not part of the initial scene setup
// Returns true if initial scene setup is restored successfully
public bool RestoreInitialSceneSetup()
{
if( initialSceneSetup == null || initialSceneSetup.Length == 0 )
return true;
if( EditorApplication.isPlaying )
return false;
if( !IsSceneSetupDifferentThanCurrentSetup() )
return true;
StringBuilder sb = Utilities.stringBuilder;
sb.Length = 0;
sb.AppendLine( "Restore initial scene setup?" );
for( int i = 0; i < initialSceneSetup.Length; i++ )
sb.AppendLine().Append( "- " ).Append( initialSceneSetup[i].path );
switch( EditorUtility.DisplayDialogComplex( "Asset Usage Detector", sb.ToString(), "Yes", "Cancel", "Leave it as is" ) )
{
case 1: return false;
case 2: return true;
}
if( !EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo() )
return false;
for( int i = 0; i < initialSceneSetup.Length; i++ )
{
Scene scene = EditorSceneManager.GetSceneByPath( initialSceneSetup[i].path );
if( !scene.isLoaded )
scene = EditorSceneManager.OpenScene( initialSceneSetup[i].path, initialSceneSetup[i].isLoaded ? OpenSceneMode.Additive : OpenSceneMode.AdditiveWithoutLoading );
if( initialSceneSetup[i].isActive )
EditorSceneManager.SetActiveScene( scene );
}
SceneSetup[] sceneFinalSetup = EditorSceneManager.GetSceneManagerSetup();
for( int i = 0; i < sceneFinalSetup.Length; i++ )
{
bool sceneIsOneOfInitials = false;
for( int j = 0; j < initialSceneSetup.Length; j++ )
{
if( sceneFinalSetup[i].path == initialSceneSetup[j].path )
{
sceneIsOneOfInitials = true;
break;
}
}
if( !sceneIsOneOfInitials )
EditorSceneManager.CloseScene( EditorSceneManager.GetSceneByPath( sceneFinalSetup[i].path ), true );
}
for( int i = 0; i < initialSceneSetup.Length; i++ )
{
if( !initialSceneSetup[i].isLoaded )
EditorSceneManager.CloseScene( EditorSceneManager.GetSceneByPath( initialSceneSetup[i].path ), false );
}
initialSceneSetup = null;
return true;
}
// Assembly reloading; serialize nodes in a way that Unity can serialize
// Credit: https://docs.unity3d.com/Manual/script-Serialization-Custom.html
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
if( result == null )
return;
if( serializedGroups == null )
serializedGroups = new List<SerializableResultGroup>( result.Count );
else
serializedGroups.Clear();
if( serializedNodes == null )
serializedNodes = new List<SerializableNode>( result.Count * 16 );
else
serializedNodes.Clear();
Dictionary<ReferenceNode, int> nodeToIndex = new Dictionary<ReferenceNode, int>( result.Count * 16 );
for( int i = 0; i < result.Count; i++ )
serializedGroups.Add( result[i].Serialize( nodeToIndex, serializedNodes ) );
if( serializedUsedObjects == null || serializedUsedObjects.Length != UsedObjects.Count )
serializedUsedObjects = new Object[UsedObjects.Count];
UsedObjects.CopyTo( serializedUsedObjects );
}
// Assembly reloaded; deserialize nodes to construct the original graph
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
if( serializedGroups == null || serializedNodes == null || serializedUsedObjects == null )
return;
if( result == null )
result = new List<SearchResultGroup>( serializedGroups.Count );
else
result.Clear();
List<ReferenceNode> allNodes = new List<ReferenceNode>( serializedNodes.Count );
for( int i = 0; i < serializedNodes.Count; i++ )
allNodes.Add( new ReferenceNode() );
for( int i = 0; i < serializedNodes.Count; i++ )
allNodes[i].Deserialize( serializedNodes[i], allNodes );
for( int i = 0; i < serializedGroups.Count; i++ )
{
result.Add( new SearchResultGroup( serializedGroups[i].title, serializedGroups[i].type, serializedGroups[i].isExpanded, serializedGroups[i].pendingSearch ) );
result[i].Deserialize( serializedGroups[i], allNodes );
}
if( UsedObjects == null )
UsedObjects = new HashSet<Object>( serializedUsedObjects );
else
UsedObjects.UnionWith( serializedUsedObjects );
serializedNodes.Clear();
serializedGroups.Clear();
serializedUsedObjects = null;
}
IEnumerator IEnumerable.GetEnumerator() { return ( (IEnumerable) result ).GetEnumerator(); }
IEnumerator<SearchResultGroup> IEnumerable<SearchResultGroup>.GetEnumerator() { return ( (IEnumerable<SearchResultGroup>) result ).GetEnumerator(); }
}
// Custom class to hold the results for a single scene or Assets folder
public class SearchResultGroup : IEnumerable<ReferenceNode>
{
public enum GroupType { Assets = 0, Scene = 1, DontDestroyOnLoad = 2, ProjectSettings = 3, UnusedObjects = 4 };
private readonly List<ReferenceNode> references = new List<ReferenceNode>();
internal SearchResultTreeView treeView;
internal SearchResultTreeViewState treeViewState;
private Rect lastTreeViewRect;
private SearchField treeViewSearchField;
public string Title { get; private set; }
public GroupType Type { get; private set; }
public string ScenePath { get; private set; }
public bool IsExpanded { get; private set; }
public bool PendingSearch { get; private set; }
public int NumberOfReferences { get { return references.Count; } }
public ReferenceNode this[int index] { get { return references[index]; } }
public SearchResultGroup( string title, GroupType type, bool isExpanded = true, bool pendingSearch = false )
{
Title = title.StartsWith( "<b>" ) ? title : string.Concat( "<b>", title, "</b>" );
ScenePath = type != GroupType.Scene ? null : ( title.StartsWith( "<b>" ) ? title.Substring( 3, title.Length - 7 ) : title );
Type = type;
IsExpanded = isExpanded;
PendingSearch = pendingSearch;
}
public void AddReference( ReferenceNode node )
{
references.Add( node );
}
public void RemoveReference( int index )
{
references.RemoveAt( index );
}
// Removes all nodes
public void Clear()
{
PendingSearch = false;
references.Clear();
treeView = null;
treeViewState = null;
treeViewSearchField = null;
}
public void Collapse()
{
IsExpanded = false;
}
// Initializes commonly used variables of the nodes
public void InitializeNodes( HashSet<Object> objectsToSearchSet )
{
List<ReferenceNode> _references = new List<ReferenceNode>( references );
references.Clear();
// Reverse the links of the search results graph so that the root ReferenceNodes are the searched objects
Dictionary<ReferenceNode, ReferenceNode> reverseGraphNodes = new Dictionary<ReferenceNode, ReferenceNode>( references.Count * 16 );
for( int i = 0; i < _references.Count; i++ )
_references[i].CreateReverseGraphRecursively( this, references, reverseGraphNodes, objectsToSearchSet );
// Remove weak links if they aren't ultimately connected to a non-weak link
HashSet<ReferenceNode> visitedNodes = new HashSet<ReferenceNode>();
for( int i = references.Count - 1; i >= 0; i-- )
references[i].RemoveRedundantLinksRecursively( visitedNodes );
// When a GameObject is a root node, then any components of that GameObject that are also root nodes should omit their links to the
// GameObject's node because otherwise, search results are filled with redundant 'GameObject->Its Component' references
HashSet<ReferenceNode> rootGameObjectNodes = new HashSet<ReferenceNode>();
for( int i = references.Count - 1; i >= 0; i-- )
{
if( references[i].nodeObject as GameObject )
rootGameObjectNodes.Add( references[i] );
}
for( int i = references.Count - 1; i >= 0; i-- )
{
ReferenceNode node = references[i];
Component component = node.nodeObject as Component;
if( component )
{
for( int j = node.NumberOfOutgoingLinks - 1; j >= 0; j-- )
{
if( ReferenceEquals( node[j].targetNode.nodeObject, component.gameObject ) && rootGameObjectNodes.Contains( node[j].targetNode ) )
{
node.RemoveLink( j );
break;
}
}
}
}
// Remove root nodes that don't have any outgoing links
for( int i = references.Count - 1; i >= 0; i-- )
{
if( references[i].NumberOfOutgoingLinks == 0 )
references.RemoveAt( i );
}
// Sort root nodes
if( references.Count > 1 )
{
SearchResult.SortedEntry[] sortedEntries = new SearchResult.SortedEntry[references.Count];
for( int i = references.Count - 1; i >= 0; i-- )
sortedEntries[i] = new SearchResult.SortedEntry( references[i] );
Array.Sort( sortedEntries );
for( int i = 0; i < sortedEntries.Length; i++ )
references[i] = (ReferenceNode) sortedEntries[i].entry;
}
for( int i = references.Count - 1; i >= 0; i-- )
{
references[i].SortLinks(); // Sort immediate links of the root nodes
references[i].InitializeRecursively();
}
}
// Draw the results found for this container
public float DrawOnGUI( SearchResult searchResult, EditorWindow window, float scrollPosition, bool noAssetDatabaseChanges )
{
Event ev = Event.current;
Color c = GUI.backgroundColor;
float headerHeight = EditorGUIUtility.singleLineHeight * 2f;
float refreshButtonWidth = 100f;
GUI.backgroundColor = AssetUsageDetectorSettings.SearchResultGroupHeaderColor;
Rect headerRect = EditorGUILayout.GetControlRect( false, headerHeight );
float width = headerRect.width;
headerRect.width = headerHeight;
if( GUI.Button( headerRect, IsExpanded ? "v" : ">" ) )
{
IsExpanded = !IsExpanded;
if( ev.alt && treeView != null )
{
if( !IsExpanded )
treeView.CollapseAll();
else
treeView.ExpandAll();
}
window.Repaint();
GUIUtility.ExitGUI();
}
headerRect.x += headerHeight;
headerRect.width = width - ( searchResult != null ? ( refreshButtonWidth + headerHeight ) : headerHeight );
if( GUI.Button( headerRect, Title, Utilities.BoxGUIStyle ) )
{
if( ev.button != 1 )
{
if( Type == GroupType.Scene )
{
// If the container (scene, usually) is left clicked, highlight it on Project view
SceneAsset sceneAsset = AssetDatabase.LoadAssetAtPath<SceneAsset>( ScenePath );
if( sceneAsset )
{
if( AssetUsageDetectorSettings.PingClickedObjects )
EditorGUIUtility.PingObject( sceneAsset );
if( AssetUsageDetectorSettings.SelectClickedObjects )
Selection.activeObject = sceneAsset;
}
}
}
else
{
GenericMenu contextMenu = new GenericMenu();
if( searchResult != null )
contextMenu.AddItem( new GUIContent( "Hide" ), false, () => searchResult.RemoveSearchResultGroup( this ) );
if( references.Count > 0 && treeView != null )
{
if( contextMenu.GetItemCount() > 0 )
contextMenu.AddSeparator( "" );
if( Type != GroupType.UnusedObjects )
{
contextMenu.AddItem( new GUIContent( "Expand Direct References Only" ), false, () =>
{
treeView.ExpandDirectReferences();
IsExpanded = true;
} );
contextMenu.AddItem( new GUIContent( "Expand Main References Only" ), false, () =>
{
treeView.ExpandMainReferences();
IsExpanded = true;
} );
}
if( !string.IsNullOrEmpty( treeViewState.searchTerm ) && treeViewState.searchMode == SearchResultTreeView.SearchMode.ReferencesOnly )
{
contextMenu.AddItem( new GUIContent( "Expand Matching Search Results Only" ), false, () =>
{
treeView.ExpandMatchingSearchResults();
IsExpanded = true;
} );
}
contextMenu.AddItem( new GUIContent( "Expand All" ), false, () =>
{
treeView.ExpandAll();
IsExpanded = true;
} );
contextMenu.AddItem( new GUIContent( "Collapse All" ), false, () =>
{
treeView.CollapseAll();
IsExpanded = true;
} );
if( searchResult != null && searchResult.NumberOfGroups > 1 && !string.IsNullOrEmpty( treeViewState.searchTerm ) )
{
if( contextMenu.GetItemCount() > 0 )
contextMenu.AddSeparator( "" );
contextMenu.AddItem( new GUIContent( "Apply Search to All Results" ), false, () =>
{
for( int i = 0; i < searchResult.NumberOfGroups; i++ )
{
if( searchResult[i].treeView == null )
continue;
string previousSearchTerm = searchResult[i].treeViewState.searchTerm ?? "";
SearchResultTreeView.SearchMode previousSearchMode = searchResult[i].treeViewState.searchMode;
searchResult[i].treeViewState.searchTerm = treeViewState.searchTerm ?? "";
searchResult[i].treeViewState.searchMode = treeViewState.searchMode;
if( treeViewState.searchTerm != previousSearchTerm || treeViewState.searchMode != previousSearchMode )
searchResult[i].treeView.RefreshSearch( previousSearchTerm );
}
} );
}
}
#if UNITY_2022_2_OR_NEWER
int loadedSceneCount = SceneManager.loadedSceneCount;
#else
int loadedSceneCount = EditorSceneManager.loadedSceneCount;
#endif
if( Type == GroupType.Scene && !EditorApplication.isPlaying && loadedSceneCount > 1 )
{
// Show context menu when SearchResultGroup's header is right clicked
Scene scene = EditorSceneManager.GetSceneByPath( ScenePath );
if( scene.isLoaded )
{
if( contextMenu.GetItemCount() > 0 )
contextMenu.AddSeparator( "" );
contextMenu.AddItem( new GUIContent( "Close Scene" ), false, () =>
{
if( !scene.isDirty || EditorSceneManager.SaveModifiedScenesIfUserWantsTo( new Scene[1] { scene } ) )
EditorSceneManager.CloseScene( scene, true );
} );
}
}
contextMenu.ShowAsContext();
}
}
if( searchResult != null )
{
bool guiEnabled = GUI.enabled;
GUI.enabled = Type != GroupType.UnusedObjects;
headerRect.x += width - ( refreshButtonWidth + headerHeight );
headerRect.width = refreshButtonWidth;
if( GUI.Button( headerRect, "Refresh" ) )
{
searchResult.RefreshSearchResultGroup( this, noAssetDatabaseChanges );
GUIUtility.ExitGUI();
}
GUI.enabled = guiEnabled;
}
GUI.backgroundColor = c;
if( IsExpanded )
{
if( PendingSearch )
GUILayout.Box( "Lazy Search: this scene potentially has some references, hit Refresh to find them", Utilities.BoxGUIStyle );
else if( references.Count == 0 )
GUILayout.Box( ( Type == GroupType.UnusedObjects ) ? "No unused objects left..." : "No references found...", Utilities.BoxGUIStyle );
else
{
if( Type == GroupType.UnusedObjects )
{
if( searchResult != null && searchResult.HasPendingLazySceneSearchResults )
EditorGUILayout.HelpBox( "Some scene(s) aren't searched yet (lazy scene search). Refreshing those scene(s) will automatically update this list.", MessageType.Warning );
if( searchResult != null && searchResult.SearchParameters.dontSearchInSourceAssets && searchResult.SearchParameters.objectsToSearch.Length > 1 )
EditorGUILayout.HelpBox( "'Don't search \"SEARCHED OBJECTS\" themselves for references' is enabled, some of these objects might be used by \"SEARCHED OBJECTS\".", MessageType.Warning );
if( !AssetUsageDetectorSettings.MarkUsedAssetsSubAssetsAsUsed )
EditorGUILayout.HelpBox( "'Hide unused sub-assets in \"Unused Objects\" list if their parent assets are used' is disabled, unused sub-assets' parent assets might be used.", MessageType.Warning );
EditorGUILayout.HelpBox( "Although no references to these objects are found, they might still be used somewhere (e.g. via Resources.Load). If you intend to delete these objects, consider creating a backup of your project first.", MessageType.Info );
}
if( treeView == null )
{
bool isFirstInitialization = ( treeViewState == null );
if( isFirstInitialization )
treeViewState = new SearchResultTreeViewState();
// This isn't inside isFirstInitialization because SearchResultTreeViewState might have been initialized by
// Unity's serialization system after a domain reload
bool shouldUpdateInitialTreeViewNodeId = ( treeViewState.initialNodeId == 0 && searchResult != null );
if( shouldUpdateInitialTreeViewNodeId )
treeViewState.initialNodeId = searchResult.nextTreeViewId;
treeView = new SearchResultTreeView( treeViewState, references, ( Type == GroupType.UnusedObjects ) ? SearchResultTreeView.TreeType.UnusedObjects : SearchResultTreeView.TreeType.Normal, searchResult != null ? searchResult.UsedObjects : null, searchResult != null && searchResult.SearchParameters.hideDuplicateRows, searchResult != null && searchResult.SearchParameters.hideReduntantPrefabVariantLinks, true );
if( isFirstInitialization )
{
if( Type != GroupType.UnusedObjects )
treeView.ExpandMainReferences();
else
treeView.ExpandAll();
}
if( shouldUpdateInitialTreeViewNodeId )
searchResult.nextTreeViewId = treeViewState.finalNodeId;
}
if( treeViewSearchField == null )
{
treeViewSearchField = new SearchField() { autoSetFocusOnFindCommand = false };
treeViewSearchField.downOrUpArrowKeyPressed += () => treeView.SetFocusAndEnsureSelectedItem(); // Not assigning SetFocusAndEnsureSelectedItem directly in case treeView's value changes
}
Rect searchFieldRect = EditorGUILayout.GetControlRect( false, EditorGUIUtility.singleLineHeight );
string previousSearchTerm = treeViewState.searchTerm ?? "";
SearchResultTreeView.SearchMode previousSearchMode = treeViewState.searchMode;
treeViewState.searchTerm = treeViewSearchField.OnToolbarGUI( new Rect( searchFieldRect.x, searchFieldRect.y, searchFieldRect.width - 100f, searchFieldRect.height ), treeViewState.searchTerm ) ?? "";
treeViewState.searchMode = (SearchResultTreeView.SearchMode) EditorGUI.EnumPopup( new Rect( searchFieldRect.xMax - 100f, searchFieldRect.y, 100f, searchFieldRect.height ), treeViewState.searchMode );
if( treeViewState.searchTerm != previousSearchTerm || treeViewState.searchMode != previousSearchMode )
treeView.RefreshSearch( previousSearchTerm );
KeyCode pressedKeyboardNavigationKey = KeyCode.None;
bool treeViewKeyboardNavigation = false;
if( ev.type == EventType.KeyDown )
{
pressedKeyboardNavigationKey = ev.keyCode;
switch( pressedKeyboardNavigationKey )
{
case KeyCode.UpArrow:
case KeyCode.DownArrow:
case KeyCode.LeftArrow:
case KeyCode.RightArrow:
case KeyCode.PageUp:
case KeyCode.PageDown:
case KeyCode.Home:
case KeyCode.End:
case KeyCode.F: treeViewKeyboardNavigation = true; break;
}
SearchResultTooltip.Hide();
}
else if( ( ev.type == EventType.ValidateCommand || ev.type == EventType.ExecuteCommand ) && ev.commandName == "Find" && treeView.HasFocus() )
{
if( ev.type == EventType.ExecuteCommand )
{
treeViewSearchField.SetFocus();
// Framed rect padding: Top = 2, Bottom = 2 + the first element in the TreeView
scrollPosition = FrameRectInScrollView( scrollPosition, new Vector2( searchFieldRect.y - 2f, searchFieldRect.yMax + EditorGUIUtility.singleLineHeight + 2f ), window.position.height );
window.Repaint();
SearchResultTooltip.Hide();
}
ev.Use();
}
else if( ev.type == EventType.ScrollWheel )
SearchResultTooltip.Hide();
bool isFirstRowSelected = false, isLastRowSelected = false, isSelectedRowExpanded = false, canExpandSelectedRow = false;
if( treeViewKeyboardNavigation && treeView.HasFocus() && treeView.HasSelection() )
treeView.GetRowStateWithId( treeViewState.lastClickedID, out isFirstRowSelected, out isLastRowSelected, out isSelectedRowExpanded, out canExpandSelectedRow );
Rect treeViewRect = EditorGUILayout.GetControlRect( false, treeView.totalHeight );
if( ev.type == EventType.Repaint )
{
lastTreeViewRect = treeViewRect;
#if !UNITY_2018_2_OR_NEWER
// TreeView calls RowGUI for all rows instead of only the visible rows on early Unity versions which leads to performance issues. Do manual row culling on those versions
// Credit: https://github.com/Unity-Technologies/UnityCsReference/blob/a048de916b23331bf6dfe92c4a6c205989b83b4f/Editor/Mono/GUI/TreeView/TreeViewGUI.cs#L273-L276
float topPixel = scrollPosition - treeViewRect.y;
float heightInPixels = window.position.height;
treeView.visibleRowTop = (int) Mathf.Floor( topPixel / treeView.rowHeight );
treeView.visibleRowBottom = treeView.visibleRowTop + (int) Mathf.Ceil( heightInPixels / treeView.rowHeight );
#endif
}
treeView.OnGUI( treeViewRect );
if( treeViewKeyboardNavigation && treeView.HasFocus() && treeView.HasSelection() )
{
Rect targetTreeViewRowRect;
if( treeView.GetRowRectWithId( treeViewState.lastClickedID, out targetTreeViewRowRect ) )
{
// Allow keyboard navigation between different SearchResultGroups' TreeViews
Rect targetTreeViewRect = lastTreeViewRect;
if( !ev.control && !ev.command && !ev.shift )
{
if( isFirstRowSelected && ( pressedKeyboardNavigationKey == KeyCode.UpArrow || pressedKeyboardNavigationKey == KeyCode.PageUp || pressedKeyboardNavigationKey == KeyCode.Home || ( pressedKeyboardNavigationKey == KeyCode.LeftArrow && !isSelectedRowExpanded ) ) )
{
int searchResultGroupIndex = searchResult.IndexOf( this );
for( int i = searchResultGroupIndex - 1; i >= 0; i-- )
{
if( !searchResult[i].PendingSearch && searchResult[i].IsExpanded && searchResult[i].references.Count > 0 )
{
searchResult[i].treeView.SetFocus();
targetTreeViewRect = searchResult[i].lastTreeViewRect;
targetTreeViewRowRect = searchResult[i].treeView.SelectLastRowAndReturnRect();
break;
}
}
}
else if( isLastRowSelected && ( pressedKeyboardNavigationKey == KeyCode.DownArrow || pressedKeyboardNavigationKey == KeyCode.PageDown || pressedKeyboardNavigationKey == KeyCode.End || ( pressedKeyboardNavigationKey == KeyCode.RightArrow && !canExpandSelectedRow ) ) )
{
int searchResultGroupIndex = searchResult.IndexOf( this );
for( int i = searchResultGroupIndex + 1; i < searchResult.NumberOfGroups; i++ )
{
if( !searchResult[i].PendingSearch && searchResult[i].IsExpanded && searchResult[i].references.Count > 0 )
{
searchResult[i].treeView.SetFocus();
targetTreeViewRect = searchResult[i].lastTreeViewRect;
targetTreeViewRowRect = searchResult[i].treeView.SelectFirstRowAndReturnRect();
break;
}
}
}
}
// When key event isn't automatically used by the focused TreeView (happens when its search results are empty), if we navigate to
// a new TreeView, key event will be consumed by that TreeView and hence, keyboard navigation will occur twice
if( ev.type != EventType.Used )
ev.Use();
float scrollTop = targetTreeViewRect.y + targetTreeViewRowRect.y;
float scrollBottom = targetTreeViewRect.y + targetTreeViewRowRect.yMax;
scrollPosition = FrameRectInScrollView( scrollPosition, new Vector2( scrollTop, scrollBottom ), window.position.height );
window.Repaint();
}
}
}
}
return scrollPosition;
}
// Frame selection (it isn't handled automatically when using an external scroll view)
// Credit: https://github.com/Unity-Technologies/UnityCsReference/blob/d0fe81a19ce788fd1d94f826cf797aafc37db8ea/Editor/Mono/GUI/TreeView/TreeViewController.cs#L1329-L1351
private float FrameRectInScrollView( float scrollPosition, Vector2 rectBounds, float windowHeight )
{
return Mathf.Clamp( scrollPosition, rectBounds.y - windowHeight, rectBounds.x );
}
// Serialize this result group
internal SearchResult.SerializableResultGroup Serialize( Dictionary<ReferenceNode, int> nodeToIndex, List<SearchResult.SerializableNode> serializedNodes )
{
SearchResult.SerializableResultGroup serializedResultGroup = new SearchResult.SerializableResultGroup()
{
title = Title,
type = Type,
isExpanded = IsExpanded,
pendingSearch = PendingSearch,
treeViewState = treeViewState
};
if( references != null )
{
serializedResultGroup.initialSerializedNodes = new List<int>( references.Count );
for( int i = 0; i < references.Count; i++ )
serializedResultGroup.initialSerializedNodes.Add( references[i].SerializeRecursively( nodeToIndex, serializedNodes ) );
}
return serializedResultGroup;
}
// Deserialize this result group from the serialized data
internal void Deserialize( SearchResult.SerializableResultGroup serializedResultGroup, List<ReferenceNode> allNodes )
{
treeViewState = serializedResultGroup.treeViewState;
if( serializedResultGroup.initialSerializedNodes != null )
{
for( int i = 0; i < serializedResultGroup.initialSerializedNodes.Count; i++ )
references.Add( allNodes[serializedResultGroup.initialSerializedNodes[i]] );
}
}
IEnumerator IEnumerable.GetEnumerator() { return ( (IEnumerable) references ).GetEnumerator(); }
IEnumerator<ReferenceNode> IEnumerable<ReferenceNode>.GetEnumerator() { return ( (IEnumerable<ReferenceNode>) references ).GetEnumerator(); }
}
// Custom class to hold an object in the path to a reference as a node
public class ReferenceNode
{
internal enum UsedState { Unused, MixedCollapsed, MixedExpanded, Used };
public class Link
{
public readonly ReferenceNode targetNode;
public readonly List<string> descriptions;
public bool isWeakLink; // Weak links can be omitted from search results if this ReferenceNode isn't referenced by any other node
public Link( ReferenceNode targetNode, string description, bool isWeakLink )
{
this.targetNode = targetNode;
this.descriptions = string.IsNullOrEmpty( description ) ? new List<string>() : new List<string>( 1 ) { description };
this.isWeakLink = isWeakLink;
}
public Link( ReferenceNode targetNode, List<string> descriptions, bool isWeakLink )
{
this.targetNode = targetNode;
this.descriptions = descriptions;
this.isWeakLink = isWeakLink;
}
}
// Unique identifier is used while serializing the node
private static int uid_last = 0;
private readonly int uid;
public string Label { get; private set; }
public bool IsMainReference { get; private set; } // True: if belongs to a scene search result group, then it's an object in that scene. If belongs to the assets search result group, then it's an asset
internal object nodeObject;
private int? instanceId; // instanceId of the nodeObject if it is a Unity object, null otherwise
public Object UnityObject { get { return instanceId.HasValue ? EditorUtility.InstanceIDToObject( instanceId.Value ) : null; } }
private readonly List<Link> links = new List<Link>( 2 );
public int NumberOfOutgoingLinks { get { return links.Count; } }
public Link this[int index] { get { return links[index]; } }
internal UsedState usedState;
public ReferenceNode()
{
uid = uid_last++;
usedState = UsedState.Used;
}
// Add a one-way connection to another node
public void AddLinkTo( ReferenceNode nextNode, string description = null, bool isWeakLink = false )
{
if( nextNode != null && nextNode != this )
{
if( !string.IsNullOrEmpty( description ) )
description = "[" + description + "]";
// Avoid duplicate links
for( int i = 0; i < links.Count; i++ )
{
if( links[i].targetNode == nextNode )
{
if( !string.IsNullOrEmpty( description ) && !links[i].descriptions.Contains( description ) )
links[i].descriptions.Add( description );
links[i].isWeakLink &= isWeakLink;
return;
}
}
links.Add( new Link( nextNode, description, isWeakLink ) );
}
}
public void RemoveLink( int index )
{
links.RemoveAt( index );
}
public bool RemoveLink( ReferenceNode nextNode )
{
for( int i = links.Count - 1; i >= 0; i-- )
{
if( links[i].targetNode == nextNode )
{
links.RemoveAt( i );
return true;
}
}
return false;
}
public void SortLinks()
{
if( links.Count > 1 )
{
SearchResult.SortedEntry[] sortedEntries = new SearchResult.SortedEntry[links.Count];
for( int i = links.Count - 1; i >= 0; i-- )
sortedEntries[i] = new SearchResult.SortedEntry( links[i] );
Array.Sort( sortedEntries );
for( int i = 0; i < sortedEntries.Length; i++ )
links[i] = (Link) sortedEntries[i].entry;
}
}
public void CopyReferencesTo( ReferenceNode other )
{
other.links.Clear();
other.links.AddRange( links );
}
// Clear this node so that it can be reused later
public void Clear()
{
nodeObject = null;
links.Clear();
}
public void InitializeRecursively()
{
if( Label != null ) // Already initialized
return;
Object unityObject = nodeObject as Object;
if( unityObject != null )
{
instanceId = unityObject.GetInstanceID();
Label = unityObject.name + " (" + unityObject.GetType().Name + ")";
if( AssetUsageDetectorSettings.ShowRootAssetName && unityObject.IsAsset() && !AssetDatabase.IsMainAsset( unityObject ) )
{
string mainAssetName = Path.GetFileNameWithoutExtension( AssetDatabase.GetAssetPath( unityObject ) );
if( unityObject.name != mainAssetName )
Label += " <in " + mainAssetName + ">";
}
}
else if( nodeObject != null )
{
instanceId = null;
Label = nodeObject.GetType() + " object";
}
else
{
instanceId = null;
Label = "<<destroyed>>";
}
nodeObject = null; // Don't hold Object reference, allow Unity to GC used memory
for( int i = 0; i < links.Count; i++ )
links[i].targetNode.InitializeRecursively();
}
public ReferenceNode CreateReverseGraphRecursively( SearchResultGroup searchResultGroup, List<ReferenceNode> reverseGraphRoots, Dictionary<ReferenceNode, ReferenceNode> reverseGraphNodes, HashSet<Object> objectsToSearchSet )
{
ReferenceNode result;
if( !reverseGraphNodes.TryGetValue( this, out result ) )
{
reverseGraphNodes[this] = result = new ReferenceNode() { nodeObject = nodeObject };
Object obj = nodeObject as Object;
if( obj && objectsToSearchSet.Contains( obj ) )
reverseGraphRoots.Add( result );
//else // When 'else' is uncommented, 'Don't search "Find referenced of" themselves for references" option simply does nothing. I am not entirely sure if commenting it out will have any side effects, so fingers crossed?
{
for( int i = 0; i < links.Count; i++ )
{
ReferenceNode linkedNode = links[i].targetNode.CreateReverseGraphRecursively( searchResultGroup, reverseGraphRoots, reverseGraphNodes, objectsToSearchSet );
linkedNode.links.Add( new Link( result, links[i].descriptions, links[i].isWeakLink ) );
}
}
if( obj )
{
if( obj is Component )
obj = ( (Component) obj ).gameObject;
switch( searchResultGroup.Type )
{
case SearchResultGroup.GroupType.Assets: result.IsMainReference = obj.IsAsset() && ( obj is GameObject || ( obj.hideFlags & ( HideFlags.HideInInspector | HideFlags.HideInHierarchy ) ) == HideFlags.None ); break;
case SearchResultGroup.GroupType.ProjectSettings: result.IsMainReference = obj.IsAsset() && AssetDatabase.GetAssetPath( obj ).StartsWith( "ProjectSettings/" ); break;
case SearchResultGroup.GroupType.Scene:
case SearchResultGroup.GroupType.DontDestroyOnLoad:
{
if( obj is GameObject )
{
Scene scene = ( (GameObject) obj ).scene;
if( scene.IsValid() )
result.IsMainReference = ( searchResultGroup.Type == SearchResultGroup.GroupType.Scene ) ? scene.path == searchResultGroup.ScenePath : scene.name == "DontDestroyOnLoad";
}
break;
}
}
}
}
return result;
}
public void RemoveRedundantLinksRecursively( HashSet<ReferenceNode> visitedNodes )
{
if( !visitedNodes.Add( this ) )
return;
List<ReferenceNode> stack = null;
for( int i = links.Count - 1; i >= 0; i-- )
{
if( !links[i].isWeakLink )
continue;
if( links[i].targetNode.links.Count == 0 )
links.RemoveAt( i );
else
{
if( stack == null )
stack = new List<ReferenceNode>( 2 );
else
stack.Clear();
if( !links[i].targetNode.CheckForNonWeakLinksRecursively( stack ) )
links.RemoveAt( i );
}
}
for( int i = links.Count - 1; i >= 0; i-- )
links[i].targetNode.RemoveRedundantLinksRecursively( visitedNodes );
}
private bool CheckForNonWeakLinksRecursively( List<ReferenceNode> stack )
{
if( stack.Contains( this ) || links.Count == 0 )
return false;
for( int i = links.Count - 1; i >= 0; i-- )
{
if( !links[i].isWeakLink )
return true;
}
stack.Add( this );
for( int i = links.Count - 1; i >= 0; i-- )
{
if( links[i].targetNode.CheckForNonWeakLinksRecursively( stack ) )
return true;
}
stack.RemoveAt( stack.Count - 1 );
return false;
}
// Serialize this node and its connected nodes recursively
internal int SerializeRecursively( Dictionary<ReferenceNode, int> nodeToIndex, List<SearchResult.SerializableNode> serializedNodes )
{
int index;
if( nodeToIndex.TryGetValue( this, out index ) )
return index;
SearchResult.SerializableNode serializedNode = new SearchResult.SerializableNode()
{
label = Label,
isMainReference = IsMainReference,
instanceId = instanceId ?? 0,
isUnityObject = instanceId.HasValue,
usedState = usedState
};
index = serializedNodes.Count;
nodeToIndex[this] = index;
serializedNodes.Add( serializedNode );
if( links.Count > 0 )
{
serializedNode.links = new List<int>( links.Count );
serializedNode.linkDescriptions = new List<SearchResult.SerializableNode.SerializableLinkDescriptions>( links.Count );
serializedNode.linkWeakStates = new List<bool>( links.Count );
for( int i = 0; i < links.Count; i++ )
{
serializedNode.links.Add( links[i].targetNode.SerializeRecursively( nodeToIndex, serializedNodes ) );
serializedNode.linkDescriptions.Add( new SearchResult.SerializableNode.SerializableLinkDescriptions() { value = links[i].descriptions } );
serializedNode.linkWeakStates.Add( links[i].isWeakLink );
}
}
return index;
}
// Deserialize this node and its links from the serialized data
internal void Deserialize( SearchResult.SerializableNode serializedNode, List<ReferenceNode> allNodes )
{
if( serializedNode.isUnityObject )
instanceId = serializedNode.instanceId;
else
instanceId = null;
Label = serializedNode.label;
IsMainReference = serializedNode.isMainReference;
usedState = serializedNode.usedState;
if( serializedNode.links != null )
{
for( int i = 0; i < serializedNode.links.Count; i++ )
links.Add( new Link( allNodes[serializedNode.links[i]], serializedNode.linkDescriptions[i].value, serializedNode.linkWeakStates[i] ) );
}
}
public override int GetHashCode()
{
return uid;
}
}
}