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
}
}