using System; using System.Collections.Generic; using TNodeCore.Runtime; using TNodeCore.Runtime.Components; using TNodeCore.Runtime.Models; namespace TNode.TNodeCore.Runtime.Tools{ public class GraphTool{ /// /// Topological order of the graph nodes /// [NonSerialized] public readonly List TopologicalOrder = new List(); public RuntimeGraph Parent; public bool TopologicalSorted = false; /// /// Entry nodes of the graph. These are the nodes that has no input. /// [NonSerialized] public readonly List NonDependencyNode = new List(); /// /// Cached data for Dependency traversal. /// public readonly Dictionary OutputCached = new Dictionary(); /// /// Ssed to detect if the graph tool is caching the output data of the node /// private bool _isCachingOutput = false; /// /// elements are read only ,do not modify them /// public readonly Dictionary RuntimeNodes; //Traverse and process all nodes in a topological order,dependency of the node is already resolved.if you want to run specific node,you can use RunNodeDependently instead public void DirectlyTraversal(){ foreach (var node in TopologicalOrder){ var links = node.InputLink; foreach (var link in links){ HandlingLink(link); } node.NodeData.Process(); } } /// /// Cache out port data in the graph tool so that we can directly access the output. /// The two function assume there will be no change happens in scene nodes or blackboard referenced data during the running,so in a dependency traversal for some /// batch of nodes.the nodes could directly access the output data in the graph tool instead of waiting dependency traversal resolve the result of the output. /// public void StartCachingPort(){ _isCachingOutput = true; } public void EndCachingPort(){ _isCachingOutput = false; OutputCached.Clear(); } /// /// Resolve dependencies by a deep first search,the depended nodes will be processed to satisfy the need of the the given runtime node /// Note it's a recursive function.if you want directly traverse all nodes with dependency resolved ,use DirectlyTraversal() instead. /// /// The node you want to resolve dependency /// search depth,no need provide a number when use outside public void RunNodeDependently(RuntimeNode runtimeNode,int dependencyLevel=0){ var links = runtimeNode.InputLink; foreach (var link in links){ var outputNode = RuntimeNodes[link.outPort.nodeDataId]; RunNodeDependently(outputNode,dependencyLevel+1); HandlingLink(link); } if (dependencyLevel > DependencyLevelMax){ throw new Exception("Dependency anomaly detected,check if there is a loop in the graph"); } //if the runtime node has no output ,it will not be processed if (runtimeNode.OutputLink.Count == 0 && dependencyLevel != 0){ return; } runtimeNode.NodeData.Process(); Parent.StartCoroutine(runtimeNode.NodeData.AfterProcess()); } /// /// Max depth of dependency traversal,in case of some special situation. the dependency level bigger than this number will be considered as a loop. /// private const int DependencyLevelMax = 1145; /// /// Handling a node link to transfer data from it's output side to the input side /// /// Link you want to process public void HandlingLink(NodeLink nodeLink){ //out node is node output data //in node is node receive data var inNode = RuntimeNodes[nodeLink.inPort.nodeDataId]; var outNode = RuntimeNodes[nodeLink.outPort.nodeDataId]; //TODO looks like this string would be too long to make a cache var cachedKey = $"{outNode.NodeData.id}-{nodeLink.inPort.portEntryName}"; var outValue = OutputCached.ContainsKey(cachedKey) ? OutputCached[cachedKey] : outNode.GetOutput(nodeLink.outPort.portEntryName);; if (_isCachingOutput){ OutputCached[cachedKey] = outValue; } inNode.SetInput(nodeLink.inPort.portEntryName, outValue); } /// /// Constructor of the graph tool,it will traverse the graph and build the topological order of the graph. /// /// List of nodes you need to traversal to build graph tool /// Map stores the mapping of node data id to runtime node public GraphTool(List list, Dictionary graphNodes,RuntimeGraph graph){ RuntimeNodes = graphNodes; Parent = graph; if (list == null) return; Queue queue = new Queue(); Dictionary inDegreeCounterForTopologicalSort = new Dictionary(); foreach (var runtimeNode in list){ var id = runtimeNode.NodeData.id; if (!inDegreeCounterForTopologicalSort.ContainsKey(id)){ inDegreeCounterForTopologicalSort.Add(id,runtimeNode.InputLink.Count); } if (inDegreeCounterForTopologicalSort[id] == 0){ queue.Enqueue(runtimeNode); NonDependencyNode.Add(runtimeNode); } } //Topological sort while (queue.Count > 0){ var node = queue.Dequeue(); TopologicalOrder.Add(node); foreach (var outputLink in node.OutputLink){ inDegreeCounterForTopologicalSort[outputLink.inPort.nodeDataId]--; if (inDegreeCounterForTopologicalSort[outputLink.inPort.nodeDataId] == 0){ queue.Enqueue(RuntimeNodes[outputLink.inPort.nodeDataId]); } } } TopologicalSorted = TopologicalOrder.Count != list.Count; inDegreeCounterForTopologicalSort.Clear(); queue.Clear(); } } }