389 lines
15 KiB
C#
389 lines
15 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using UnityEditor;
|
|
using UnityEditor.IMGUI.Controls;
|
|
using UnityEngine;
|
|
|
|
namespace HeurekaGames.AssetHunterPRO.BaseTreeviewImpl.DependencyGraph
|
|
{
|
|
[Serializable]
|
|
public class AH_DependencyGraphManager : ScriptableSingleton<AH_DependencyGraphManager>, ISerializationCallbackReceiver
|
|
{
|
|
[SerializeField] public bool IsDirty = true;
|
|
[SerializeField] private TreeViewState treeViewStateFrom;
|
|
[SerializeField] private TreeViewState treeViewStateTo;
|
|
[SerializeField] private MultiColumnHeaderState multiColumnHeaderStateFrom;
|
|
[SerializeField] private MultiColumnHeaderState multiColumnHeaderStateTo;
|
|
[SerializeField] private AH_DepGraphTreeviewWithModel TreeViewModelFrom;
|
|
[SerializeField] private AH_DepGraphTreeviewWithModel TreeViewModelTo;
|
|
[SerializeField] private Dictionary<string, List<string>> referencedFrom = new Dictionary<string, List<string>>();
|
|
[SerializeField] private Dictionary<string, List<string>> referenceTo = new Dictionary<string, List<string>>();
|
|
|
|
#region serializationHelpers
|
|
[SerializeField] private List<string> _keysFrom = new List<string>();
|
|
[SerializeField] private List<AH_WrapperList> _wrapperValuesFrom = new List<AH_WrapperList>();
|
|
|
|
[SerializeField] private List<string> _keysTo = new List<string>();
|
|
[SerializeField] private List<AH_WrapperList> _wrapperValuesTo = new List<AH_WrapperList>();
|
|
#endregion
|
|
|
|
[SerializeField] private string selectedAssetGUID = "";
|
|
[SerializeField] private string selectedAssetObjectName = "";
|
|
[SerializeField] private UnityEngine.Object selectedAssetObject;
|
|
|
|
[SerializeField] private List<string> selectionHistory = new List<string>();
|
|
[SerializeField] private int selectionHistoryIndex = 0;
|
|
|
|
private bool lockedSelection;
|
|
public bool LockedSelection
|
|
{
|
|
get { return lockedSelection; }
|
|
set
|
|
{
|
|
lockedSelection = value;
|
|
|
|
//Update currently selected
|
|
if (lockedSelection == false)
|
|
{
|
|
requiresRefresh = true;
|
|
UpdateSelectedAsset(Selection.activeObject);
|
|
}
|
|
}
|
|
}
|
|
public bool TraversingHistory { get; set; }
|
|
|
|
//Force window to refresh selection
|
|
private bool requiresRefresh;
|
|
|
|
|
|
//We clear history when project changes, as there are problems in identifying if history points to deleted assets
|
|
internal void ResetHistory()
|
|
{
|
|
var obsoleteAssets = selectionHistory.FindAll(x => AssetDatabase.LoadMainAssetAtPath(x) == null);
|
|
//Remove the objets that are no longer in asset db
|
|
selectionHistory.RemoveAll(x => obsoleteAssets.Contains(x));
|
|
|
|
var duplicateCount = obsoleteAssets.Count;
|
|
for (int i = selectionHistory.Count - 1; i >= 0; i--)
|
|
{
|
|
//Find identical IDs directly after each other
|
|
if (i > 0 && selectionHistory[i] == selectionHistory[i - 1])
|
|
{
|
|
selectionHistory.RemoveAt(i);
|
|
duplicateCount++;
|
|
}
|
|
}
|
|
//Reset history index to match new history
|
|
selectionHistoryIndex -= duplicateCount;
|
|
}
|
|
|
|
public string SearchString
|
|
{
|
|
get
|
|
{
|
|
return treeViewStateFrom.searchString;
|
|
}
|
|
set
|
|
{
|
|
var tmp = treeViewStateFrom.searchString;
|
|
|
|
treeViewStateFrom.searchString = treeViewStateTo.searchString = value;
|
|
if (tmp != value)
|
|
{
|
|
TreeViewModelTo.Reload();
|
|
TreeViewModelFrom.Reload();
|
|
}
|
|
}
|
|
}
|
|
|
|
//Return selected asset
|
|
public UnityEngine.Object SelectedAsset { get { return selectedAssetObject; } }
|
|
|
|
public void OnEnable()
|
|
{
|
|
hideFlags = HideFlags.HideAndDontSave;
|
|
}
|
|
|
|
public void Initialize(SearchField searchField, Rect multiColumnTreeViewRect)
|
|
{
|
|
int referenceID = 0;
|
|
initTreeview(ref treeViewStateFrom, ref multiColumnHeaderStateFrom, multiColumnTreeViewRect, ref TreeViewModelFrom, searchField, referencedFrom, selectedAssetGUID, ref referenceID);
|
|
initTreeview(ref treeViewStateTo, ref multiColumnHeaderStateTo, multiColumnTreeViewRect, ref TreeViewModelTo, searchField, referenceTo, selectedAssetGUID, ref referenceID);
|
|
|
|
requiresRefresh = false;
|
|
}
|
|
|
|
public void RefreshReferenceGraph()
|
|
{
|
|
referenceTo = new Dictionary<string, List<string>>();
|
|
referencedFrom = new Dictionary<string, List<string>>();
|
|
|
|
var paths = AssetDatabase.GetAllAssetPaths();
|
|
var pathCount = paths.Length;
|
|
|
|
for (int i = 0; i < pathCount; i++)
|
|
{
|
|
var path = paths[i];
|
|
if (AssetDatabase.IsValidFolder(path) || !path.StartsWith("Assets")) //Slow, could be done recusively
|
|
continue;
|
|
|
|
if (EditorUtility.DisplayCancelableProgressBar("Creating Reference Graph", path, ((float)i / (float)pathCount)))
|
|
{
|
|
referenceTo = new Dictionary<string, List<string>>();
|
|
referencedFrom = new Dictionary<string, List<string>>();
|
|
break;
|
|
}
|
|
|
|
var allRefs = AssetDatabase.GetDependencies(path, false);
|
|
string assetPathGuid = AssetDatabase.AssetPathToGUID(path);
|
|
|
|
List<string> newList = allRefs.Where(val => val != path).Select(val => AssetDatabase.AssetPathToGUID(val)).ToList();
|
|
|
|
//Store everything reference by this asset
|
|
if (newList.Count > 0)
|
|
referenceTo.Add(assetPathGuid, newList);
|
|
|
|
//Foreach asset refenced by this asset, store the connection
|
|
foreach (var reference in allRefs)
|
|
{
|
|
string refGuid = AssetDatabase.AssetPathToGUID(reference);
|
|
|
|
if (!referencedFrom.ContainsKey(refGuid))
|
|
referencedFrom.Add(refGuid, new List<string>());
|
|
|
|
referencedFrom[refGuid].Add(assetPathGuid);
|
|
}
|
|
}
|
|
|
|
IsDirty = false;
|
|
EditorUtility.ClearProgressBar();
|
|
}
|
|
|
|
private void initTreeview(ref TreeViewState _treeViewState, ref MultiColumnHeaderState _headerState, Rect _rect, ref AH_DepGraphTreeviewWithModel _treeView, SearchField _searchField, Dictionary<string, List<string>> referenceDict, string assetGUID, ref int referenceID)
|
|
{
|
|
bool hasValidReferences;
|
|
var treeModel = new TreeModel<AH_DepGraphElement>(getTreeViewData(referenceDict, assetGUID, out hasValidReferences, ref referenceID));
|
|
|
|
// Check if it already exists (deserialized from window layout file or scriptable object)
|
|
if (_treeViewState == null)
|
|
_treeViewState = new TreeViewState();
|
|
|
|
bool firstInit = _headerState == null;
|
|
var headerState = AH_DepGraphTreeviewWithModel.CreateDefaultMultiColumnHeaderState(_rect.width);
|
|
if (MultiColumnHeaderState.CanOverwriteSerializedFields(_headerState, headerState))
|
|
MultiColumnHeaderState.OverwriteSerializedFields(_headerState, headerState);
|
|
_headerState = headerState;
|
|
|
|
var multiColumnHeader = new AH_DepGraphHeader(_headerState);
|
|
//if (firstInit)
|
|
multiColumnHeader.ResizeToFit();
|
|
|
|
_treeView = new AH_DepGraphTreeviewWithModel(_treeViewState, multiColumnHeader, treeModel);
|
|
_searchField.downOrUpArrowKeyPressed += _treeView.SetFocusAndEnsureSelectedItem;
|
|
}
|
|
|
|
internal void UpdateTreeData(ref AH_DepGraphTreeviewWithModel _treeView, Dictionary<string, List<string>> referenceDict, string assetGUID, ref int referenceID)
|
|
{
|
|
bool hasValidReferences;
|
|
_treeView.treeModel.SetData(getTreeViewData(referenceDict, assetGUID, out hasValidReferences, ref referenceID));
|
|
}
|
|
|
|
internal bool HasCache()
|
|
{
|
|
return referencedFrom.Count > 0 && referenceTo.Count > 0;
|
|
}
|
|
|
|
internal bool HasHistory(int direction, out string tooltip)
|
|
{
|
|
int testIndex = selectionHistoryIndex + direction;
|
|
bool validIndex = (testIndex >= 0 && testIndex < selectionHistory.Count);
|
|
tooltip = validIndex ? (AssetDatabase.LoadMainAssetAtPath(selectionHistory[testIndex])?.name) : String.Empty;
|
|
//Validate that history contains that index
|
|
return (testIndex >= 0 && testIndex < selectionHistory.Count);
|
|
}
|
|
|
|
public bool HasSelection
|
|
{
|
|
get
|
|
{
|
|
return !string.IsNullOrEmpty(selectedAssetGUID);
|
|
}
|
|
}
|
|
|
|
internal bool RequiresRefresh()
|
|
{
|
|
return requiresRefresh;
|
|
}
|
|
|
|
internal AH_DepGraphTreeviewWithModel GetTreeViewTo()
|
|
{
|
|
return TreeViewModelTo;
|
|
}
|
|
|
|
internal AH_DepGraphTreeviewWithModel GetTreeViewFrom()
|
|
{
|
|
return TreeViewModelFrom;
|
|
}
|
|
|
|
internal Dictionary<string, List<string>> GetReferencesTo()
|
|
{
|
|
return referenceTo;
|
|
}
|
|
|
|
internal string GetSelectedName()
|
|
{
|
|
return selectedAssetObjectName;
|
|
}
|
|
|
|
internal Dictionary<string, List<string>> GetReferencesFrom()
|
|
{
|
|
return referencedFrom;
|
|
}
|
|
|
|
public IList<AH_DepGraphElement> getTreeViewData(Dictionary<string, List<string>> referenceDict, string assetGUID, out bool success, ref int referenceID)
|
|
{
|
|
var treeElements = new List<AH_DepGraphElement>();
|
|
|
|
int depth = -1;
|
|
|
|
var root = new AH_DepGraphElement("Root", depth, -1, "");
|
|
treeElements.Add(root);
|
|
|
|
Stack<string> referenceQueue = new Stack<string>(); //Since we are creating a tree we want the same asset to be referenced in any branch, but we do NOT want circular references
|
|
|
|
var references = referenceDict.ContainsKey(assetGUID) ? referenceDict[assetGUID] : null;
|
|
if (references != null)
|
|
{
|
|
foreach (var item in references)
|
|
{
|
|
addElement(treeElements, referenceDict, item, ref depth, ref referenceID, ref referenceQueue);
|
|
}
|
|
}
|
|
|
|
success = treeElements.Count > 2;//Did we find any references (Contains more thatn 'root' and 'self')
|
|
TreeElementUtility.ListToTree(treeElements);
|
|
|
|
EditorUtility.ClearProgressBar();
|
|
return treeElements;
|
|
}
|
|
|
|
private void addElement(List<AH_DepGraphElement> treeElements, Dictionary<string, List<string>> referenceDict, string assetGUID, ref int depth, ref int id, ref Stack<string> referenceStack)
|
|
{
|
|
var path = AssetDatabase.GUIDToAssetPath(assetGUID);
|
|
var pathSplit = path.Split('/');
|
|
|
|
if (referenceStack.Contains(path))
|
|
return;
|
|
|
|
depth++;
|
|
|
|
treeElements.Add(new AH_DepGraphElement(/*path*/pathSplit.Last(), depth, id++, path));
|
|
referenceStack.Push(path); //Add to stack to keep track of circular refs in branch
|
|
|
|
var references = referenceDict.ContainsKey(assetGUID) ? referenceDict[assetGUID] : null;
|
|
if (references != null)
|
|
foreach (var item in references)
|
|
{
|
|
addElement(treeElements, referenceDict, item, ref depth, ref id, ref referenceStack);
|
|
}
|
|
depth--;
|
|
|
|
referenceStack.Pop();
|
|
}
|
|
|
|
public void SelectPreviousFromHistory()
|
|
{
|
|
selectionHistoryIndex--;
|
|
SelectFromHistory(selectionHistoryIndex);
|
|
}
|
|
|
|
public void SelectNextFromHistory()
|
|
{
|
|
selectionHistoryIndex++;
|
|
SelectFromHistory(selectionHistoryIndex);
|
|
}
|
|
|
|
private void SelectFromHistory(int index)
|
|
{
|
|
TraversingHistory = true;
|
|
Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(selectionHistory[selectionHistoryIndex]);
|
|
}
|
|
|
|
internal void UpdateSelectedAsset(UnityEngine.Object activeObject)
|
|
{
|
|
/*if (activeObject == null)
|
|
return;*/
|
|
|
|
var invalid = activeObject == null || AssetDatabase.IsValidFolder(AssetDatabase.GetAssetPath(Selection.activeObject));
|
|
|
|
if (invalid)
|
|
{
|
|
selectedAssetGUID = selectedAssetObjectName = String.Empty;
|
|
selectedAssetObject = null;
|
|
}
|
|
else
|
|
{
|
|
selectedAssetGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(activeObject));
|
|
selectedAssetObjectName = activeObject.name;
|
|
selectedAssetObject = AssetDatabase.LoadMainAssetAtPath(AssetDatabase.GUIDToAssetPath(selectedAssetGUID));
|
|
|
|
if (!TraversingHistory)
|
|
addToHistory();
|
|
}
|
|
|
|
TraversingHistory = false;
|
|
}
|
|
|
|
private void addToHistory()
|
|
{
|
|
//Remove the part of the history branch that are no longer needed
|
|
if (selectionHistory.Count - 1 > selectionHistoryIndex)
|
|
{
|
|
selectionHistory.RemoveRange(selectionHistoryIndex, selectionHistory.Count - selectionHistoryIndex);
|
|
}
|
|
|
|
var path = AssetDatabase.GUIDToAssetPath(selectedAssetGUID);
|
|
|
|
if (selectionHistory.Count == 0 || path != selectionHistory.Last())
|
|
{
|
|
selectionHistory.Add(AssetDatabase.GUIDToAssetPath(selectedAssetGUID));
|
|
selectionHistoryIndex = selectionHistory.Count - 1;
|
|
}
|
|
}
|
|
|
|
public void OnBeforeSerialize()
|
|
{
|
|
_keysFrom.Clear();
|
|
_wrapperValuesFrom.Clear();
|
|
|
|
foreach (var kvp in referencedFrom)
|
|
{
|
|
_keysFrom.Add(kvp.Key);
|
|
_wrapperValuesFrom.Add(new AH_WrapperList(kvp.Value));
|
|
}
|
|
|
|
_keysTo.Clear();
|
|
_wrapperValuesTo.Clear();
|
|
|
|
foreach (var kvp in referenceTo)
|
|
{
|
|
_keysTo.Add(kvp.Key);
|
|
_wrapperValuesTo.Add(new AH_WrapperList(kvp.Value));
|
|
}
|
|
}
|
|
|
|
public void OnAfterDeserialize()
|
|
{
|
|
referencedFrom = new Dictionary<string, List<string>>();
|
|
for (int i = 0; i != Math.Min(_keysFrom.Count, _wrapperValuesFrom.Count); i++)
|
|
referencedFrom.Add(_keysFrom[i], _wrapperValuesFrom[i].list);
|
|
|
|
referenceTo = new Dictionary<string, List<string>>();
|
|
for (int i = 0; i != Math.Min(_keysTo.Count, _wrapperValuesTo.Count); i++)
|
|
referenceTo.Add(_keysTo[i], _wrapperValuesTo[i].list);
|
|
}
|
|
}
|
|
}
|