using System; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace AutoGroupGenerator { /// /// Command queue that identifies subgraphs within the dependency graph. /// internal class SubgraphCommandQueue : CommandQueue { #region Static Methods public static int CalculateHashForSources(IEnumerable sources) { unchecked { const uint seed = 2166136261u; // FNV-1a 32-bit seed const uint prime = 16777619u; uint hash = seed; foreach (var guid in sources .Select(s => s.Guid.ToString()) // Unity GUID is always 32 hex chars .OrderBy(g => g, StringComparer.Ordinal)) { for (int i = 0; i < guid.Length; i++) { hash ^= guid[i]; hash *= prime; } } return (int)hash; } } #endregion #region Fields private readonly DataContainer m_DataContainer; private Graph m_TransposedGraph; private Graph m_Dag; private Graph m_TransposedDag; private Dictionary m_NodeToSuperNodeMap; Dictionary> m_SuperNodeToSources; Dictionary> m_SuperNodeToPathAssets; HashSet m_InputAssets; #endregion #region Methods public SubgraphCommandQueue(DataContainer dataContainer) { m_DataContainer = dataContainer; Title = nameof(SubgraphCommandQueue); } public override void PreExecute() { ClearQueue(); m_DataContainer.Subgraphs = new Dictionary(); var nodes = m_DataContainer.DependencyGraph.GetAllNodes(); InitializeDag(); var info = "Materializing SuperNode Maps... "; foreach (var s in m_Dag.GetAllNodes()) { var localS = s; AddCommand(() => MapSuperNode(localS), info); } foreach (var node in nodes) { var localNode = node; AddCommand(() => AddNodeToSubgraph(localNode), localNode.FileName); } } public override void PostExecute() { SaveOutputReportToFile(); UnInitialize(); } private void InitializeDag() { m_InputAssets = m_DataContainer.InputAssets.Select(AssetNode.FromAssetPath).ToHashSet(); m_Dag = m_DataContainer.DependencyGraph.CollapseToSCCs(); m_TransposedDag = m_Dag.GetTransposedGraph(); m_NodeToSuperNodeMap = new(); m_SuperNodeToSources = new Dictionary>(); m_SuperNodeToPathAssets = new Dictionary>(); foreach (var superNode in m_Dag.GetAllNodes()) { foreach (var asset in superNode.Nodes) { m_NodeToSuperNodeMap[asset] = superNode; } } } void UnInitialize() { m_InputAssets = null; m_Dag = null; m_TransposedDag = null; m_NodeToSuperNodeMap = null; m_SuperNodeToSources = null; m_SuperNodeToPathAssets = null; } void MapSuperNode(SuperNode s) { m_TransposedDag.FindPathAndLeaves(s, out var path, out var superSources); var pathAssets = GetAllAssetNodes(path); var sourcesAssets = GetAllAssetNodes(superSources); m_SuperNodeToPathAssets.Add(s, pathAssets); m_SuperNodeToSources.Add(s, sourcesAssets); } private void AddNodeToSubgraph(AssetNode node) { bool sourceFound = TryFindSourcesForNode_DAG(node, out var sources); if (!sourceFound) { return; } if (sources == null || sources.Count == 0) { throw new Exception($"Cannot find source nodes for node = {node.FileName}"); } int hash = CalculateHashForSources(sources); if (m_DataContainer.Subgraphs.TryGetValue(hash, out var existingSubgraph)) { if (!existingSubgraph.Sources.SetEquals(sources)) { throw new Exception($"Hash collision = inconsistent sources for subgraph {hash}"); } } else { var newSubgraph = new Subgraph { Sources = sources, HashOfSources = hash }; m_DataContainer.Subgraphs.Add(hash, newSubgraph); } bool result = m_DataContainer.Subgraphs[hash].Nodes.Add(node); if (!result) { throw new Exception($"Unknown Error = node = {node} had added to subgraph ={hash} before"); } } private bool TryFindSourcesForNode_DAG(AssetNode targetNode, out HashSet sources) { if (!m_NodeToSuperNodeMap.TryGetValue(targetNode, out var targetSuperNode)) { throw new InvalidOperationException("AssetNode not found in collapsed DAG."); } if (m_SuperNodeToPathAssets[targetSuperNode].Overlaps(m_InputAssets)) { sources = m_SuperNodeToSources[targetSuperNode]; return true; } sources = null; return false; } private HashSet GetAllAssetNodes(HashSet superNodes) { var assetNodes = new HashSet(); foreach (var superNode in superNodes) { assetNodes.UnionWith(superNode.Nodes); } return assetNodes; } void SaveOutputReportToFile() { if (!m_DataContainer.Settings.ProcessReport.HasFlag(ProcessStepReport.SubGraphs)) return; var summary = $"Subgraphs.Count = {m_DataContainer.Subgraphs.Count} "; var data = new List(m_DataContainer.Subgraphs.Values); JsonReport.SaveJsonReport(GetType(), summary, data); } #endregion } }