diff --git a/TNodeCore/Runtime/Components/RuntimeGraph.cs b/TNodeCore/Runtime/Components/RuntimeGraph.cs index 3159915..30483ea 100644 --- a/TNodeCore/Runtime/Components/RuntimeGraph.cs +++ b/TNodeCore/Runtime/Components/RuntimeGraph.cs @@ -70,7 +70,7 @@ namespace TNodeCore.Runtime.Components{ #if UNITY_EDITOR BuildSceneNode(); #endif - _runtimeNodeEnumerator = _graphTool.BreathFirstSearch(); + ResetState(); _build = true; } @@ -171,7 +171,21 @@ namespace TNodeCore.Runtime.Components{ } public void ResetState(){ - _runtimeNodeEnumerator = _graphTool.BreathFirstSearch(); + switch (AccessMethod){ + case AccessMethod.Bfs: + _runtimeNodeEnumerator = _graphTool.BreathFirstSearch(); + break; + case AccessMethod.Dfs: + _runtimeNodeEnumerator = _graphTool.DeepFirstSearchWithCondition(); + break; + case AccessMethod.StateTransition: + _runtimeNodeEnumerator = _graphTool.IterateDirectlyTraversal(); + break; + case AccessMethod.Dependency: + _runtimeNodeEnumerator = _graphTool.IterateNext(); + break; + } + } public NodeData CurrentNode(){ @@ -268,6 +282,8 @@ namespace TNodeCore.Runtime.Components{ _graphTool.DirectlyTraversal(); } + public AccessMethod AccessMethod{ get; set; } = AccessMethod.Dependency; + public RuntimeNode GetRuntimeNode(NodeData nodeData){ if(!_build) Build(); @@ -333,8 +349,4 @@ namespace TNodeCore.Runtime.Components{ } } - public enum ProcessingStrategy{ - BreadthFirst, - DepthFirst - } } \ No newline at end of file diff --git a/TNodeCore/Runtime/RuntimeModels/IRuntimeNodeGraph.cs b/TNodeCore/Runtime/RuntimeModels/IRuntimeNodeGraph.cs index fcce0b4..a7752c7 100644 --- a/TNodeCore/Runtime/RuntimeModels/IRuntimeNodeGraph.cs +++ b/TNodeCore/Runtime/RuntimeModels/IRuntimeNodeGraph.cs @@ -3,13 +3,33 @@ using System.Collections.Generic; using TNodeCore.Runtime.Models; namespace TNodeCore.Runtime.RuntimeModels{ + public enum AccessMethod{ + //Iterate all nodes by breadth first search,start with the entry nodes and resolve the dependencies + Bfs, + //Iterate all nodes by deep first search,but run the dependencies that are not ready first + Dfs, + ///Start from the entry node,if multiple entry nodes exist,run first of them ,and then ,from this entry node,transit to nodes that met the condition,if + ///there is no node that met the condition ,stay in the state ,if there are multiple nodes that met the condition,run first of them + /// If the run node depends on other nodes,run the dependencies first. + StateTransition, + /// + /// Iterate all nodes by a topological order + /// + Dependency, + + + } + public interface IRuntimeNodeGraph{ + public AccessMethod AccessMethod{ get; set; } + public RuntimeNode GetRuntimeNode(NodeData nodeData); public RuntimeNode GetRuntimeNode(string id); public BlackboardData GetBlackboardData(); public List GetRuntimeNodes(); public Dictionary GetRuntimeNodesDictionary(); - + + public NodeData GetNode(string id); List GetRuntimeNodesOfType(Type type); List GetRuntimeNodesOfType(); diff --git a/TNodeCore/Runtime/RuntimeModels/StaticGraph.cs b/TNodeCore/Runtime/RuntimeModels/StaticGraph.cs index e184151..b648526 100644 --- a/TNodeCore/Runtime/RuntimeModels/StaticGraph.cs +++ b/TNodeCore/Runtime/RuntimeModels/StaticGraph.cs @@ -8,7 +8,7 @@ using TNodeCore.Runtime.Models; namespace TNodeCore.Runtime.RuntimeModels{ public class StaticGraph:IRuntimeNodeGraph{ private Dictionary _nodes; - private IEnumerator _breathFirstEnumerator; + private IEnumerator _runtimeNodeEnumerator; private readonly GraphTool _graphTool; private readonly GraphData _originalData; @@ -47,13 +47,29 @@ namespace TNodeCore.Runtime.RuntimeModels{ ModifyLinks(link); } _graphTool = new GraphTool(this); - _breathFirstEnumerator = _graphTool.BreathFirstSearch(); + _runtimeNodeEnumerator = _graphTool.BreathFirstSearch(); } public void ResetState(){ - _breathFirstEnumerator = _graphTool.BreathFirstSearch(); + switch (AccessMethod){ + case AccessMethod.Bfs: + _runtimeNodeEnumerator = _graphTool.BreathFirstSearch(); + break; + case AccessMethod.Dfs: + _runtimeNodeEnumerator = _graphTool.DeepFirstSearchWithCondition(); + break; + case AccessMethod.StateTransition: + _runtimeNodeEnumerator = _graphTool.IterateDirectlyTraversal(); + break; + case AccessMethod.Dependency: + _runtimeNodeEnumerator = _graphTool.IterateNext(); + break; + } + } + public AccessMethod AccessMethod{ get; set; } = AccessMethod.Bfs; + public RuntimeNode GetRuntimeNode(NodeData nodeData){ return _nodes[nodeData.id]; } @@ -92,15 +108,15 @@ namespace TNodeCore.Runtime.RuntimeModels{ } public RuntimeNode MoveNext(){ - _breathFirstEnumerator.MoveNext(); - return _breathFirstEnumerator.Current; + _runtimeNodeEnumerator.MoveNext(); + return _runtimeNodeEnumerator.Current; } public RuntimeNode CurrentRuntimeNode(){ - if (_breathFirstEnumerator.Current == null){ - _breathFirstEnumerator.MoveNext(); + if (_runtimeNodeEnumerator.Current == null){ + _runtimeNodeEnumerator.MoveNext(); } - return _breathFirstEnumerator.Current; + return _runtimeNodeEnumerator.Current; } } diff --git a/TNodeCore/Runtime/Tools/GraphTool.cs b/TNodeCore/Runtime/Tools/GraphTool.cs index 392a6a5..e577173 100644 --- a/TNodeCore/Runtime/Tools/GraphTool.cs +++ b/TNodeCore/Runtime/Tools/GraphTool.cs @@ -1,6 +1,9 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; +using TNode.TNodeCore.Runtime.Components; using TNodeCore.Runtime; using TNodeCore.Runtime.Components; using TNodeCore.Runtime.Extensions; @@ -50,68 +53,164 @@ namespace TNode.TNodeCore.Runtime.Tools{ } node.NodeData.Process(); } + } + //A IEnumerator version of the DirectlyTraversal,used to run the graph in a coroutine or somewhere you need + public IEnumerator IterateDirectlyTraversal(){ + if (TopologicalSorted==false){ + throw new Exception("The graph is not sorted,there may be a circular dependency,use another access method instead"); + } + foreach (var node in TopologicalOrder){ + var links = node.InputLinks; + foreach (var link in links){ + HandlingLink(link); + } + node.NodeData.Process(); + yield return node; + } + } + /// + /// usually used in state transition + /// + /// + public IEnumerator IterateNext(){ + var currentNode = NonDependencyNode.FirstOrDefault(); + if (currentNode == null){ + yield break; + } + + currentNode.NodeData.Process(); + yield return currentNode; + + while(currentNode.OutputLinks.Any()){ + if (currentNode is ConditionalRuntimeNode conditionalRuntimeNode){ + currentNode = RuntimeNodes[conditionalRuntimeNode.GetNextNodeId()]; + } + else{ + var link = currentNode.OutputLinks.FirstOrDefault(); + if (link != null){ + HandlingLink(link); + currentNode = RuntimeNodes[link.inPort.nodeDataId]; + } + } + currentNode.NodeData.Process(); + yield return currentNode; + } + + } //Try to enable state transition from node to node. public IEnumerator DeepFirstSearchWithCondition(){ + //Define the basic data structure for a traversal of the graph Stack stack = new Stack(); + HashSet alreadyContained = new HashSet(); + HashSet visited = new HashSet(); foreach (var runtimeNode in NonDependencyNode){ stack.Push(runtimeNode); } while (stack.Count > 0){ var node = stack.Pop(); + visited.Add(node); if (node is ConditionalRuntimeNode conditionalRuntimeNode){ var ids = conditionalRuntimeNode.GetConditionalNextIds(); var nextNodes = ids.Select(id=>RuntimeNodes[id]).ToList(); foreach (var runtimeNode in nextNodes){ - stack.Push(runtimeNode); + AddToCollectionIfMeetCondition(alreadyContained, visited,runtimeNode, stack); } } else{ - var nextNodes = node.OutputLinks.Select(link => RuntimeNodes[link.inPort.nodeDataId]); - foreach (var runtimeNode in nextNodes){ - stack.Push(runtimeNode); + foreach (var runtimeNode in node.OutputLinks.Select(link => RuntimeNodes[link.inPort.nodeDataId])){ + AddToCollectionIfMeetCondition(alreadyContained, visited,runtimeNode, stack); } - node.OutputLinks.ForEach(HandlingLink); } + node.OutputLinks.ForEach(HandlingLink); node.NodeData.Process(); yield return node; } } + /// + /// Breath first search for the graph.Not a standard BFS algorithm since all entries will be executed first. + /// + /// The IEnumerator to iterate the node public IEnumerator BreathFirstSearch(){ + //Define the basic data structure for a traversal of the graph Queue queue = new Queue(); + //Already contained method to avoid duplicate traversal HashSet alreadyContained = new HashSet(); + //Visited method to avoid duplicate traversal HashSet visited = new HashSet(); + //Firstly add all entry node to the queue foreach (var runtimeNode in NonDependencyNode){ queue.Enqueue(runtimeNode); alreadyContained.Add(runtimeNode); } + //Iterate the queue to implement bfs while (queue.Count > 0){ var node = queue.Dequeue(); visited.Add(node); + //Conditional node will be traversed in a special way,only links fit the condition will be traversed if (node is ConditionalRuntimeNode conditionalRuntimeNode){ var ids = conditionalRuntimeNode.GetConditionalNextIds(); var nextNodes = ids.Select(id=>RuntimeNodes[id]).ToList(); foreach (var runtimeNode in nextNodes){ - AddNodeToQueueIfMeetCondition(alreadyContained, runtimeNode, queue); + AddToCollectionIfMeetCondition(alreadyContained, visited,runtimeNode, queue); } } else{ foreach (var runtimeNode in node.OutputLinks.Select(link => RuntimeNodes[link.inPort.nodeDataId])){ - AddNodeToQueueIfMeetCondition(alreadyContained, runtimeNode, queue); + AddToCollectionIfMeetCondition(alreadyContained, visited,runtimeNode, queue); } - node.OutputLinks.ForEach(HandlingLink); } node.NodeData.Process(); + //Handle the links of the node + node.OutputLinks.ForEach(HandlingLink); yield return node; } } - private void AddNodeToQueueIfMeetCondition(HashSet alreadyContained, RuntimeNode runtimeNode, Queue queue){ + private void AddToCollectionIfMeetCondition(HashSet alreadyContained,HashSet visited, RuntimeNode runtimeNode, Queue queue){ + //Check if the node is already contained in the queue or already visited + + if (visited.Contains(runtimeNode)) return; + //the already contained guard is used to avoid duplicate traversal because the graph may start with multiple entries and all entry node should be run first. + //Thus cause the same node could be add to the queue multiple times. + if (alreadyContained.Contains(runtimeNode)) return; + + //Check if the visited node has all previous node of the node + var dependentNodes = runtimeNode.GetDependentNodesId().Select(x => RuntimeNodes[x]); + var allDependenciesVisited = dependentNodes.Aggregate(true, (a, b) => + alreadyContained.Contains(b) && a + ); + //If the current node is not prepared,another routine will execute it when all is ready + if (allDependenciesVisited == false) return; + + //If all conditions are met, add the node to the queue + queue.Enqueue(runtimeNode); + alreadyContained.Add(runtimeNode); + } + private void AddToCollectionIfMeetCondition(HashSet alreadyContained,HashSet visited, RuntimeNode runtimeNode, Stack stack){ + //Check if the node is already contained in the stack + if (alreadyContained.Contains(runtimeNode)) return; + if (visited.Contains(runtimeNode)) return; + //Check if the visited node has all previous node of the node + var dependentNodes = runtimeNode.GetDependentNodesId().Select(x => RuntimeNodes[x]); + var allDependenciesVisited = dependentNodes.Aggregate(true, (a, b) => + alreadyContained.Contains(b) && a + ); + //If the current node is not prepared,run it dependently. + if (allDependenciesVisited == false){ + RunNodeDependently(runtimeNode,0,false); + } + + //If all conditions are met, add the node to the stack + stack.Push(runtimeNode); + alreadyContained.Add(runtimeNode); + } + private void AddNodeToStackIfMeetCondition(HashSet alreadyContained, RuntimeNode runtimeNode, Stack stack){ //Check if the node is already contained in the queue if (alreadyContained.Contains(runtimeNode)) return; @@ -123,7 +222,7 @@ namespace TNode.TNodeCore.Runtime.Tools{ if (allDependenciesVisited == false) return; //If all conditions are met, add the node to the queue - queue.Enqueue(runtimeNode); + stack.Push(runtimeNode); alreadyContained.Add(runtimeNode); } @@ -146,10 +245,14 @@ namespace TNode.TNodeCore.Runtime.Tools{ /// /// 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){ + /// if the the node of the 0 level should be processed,which is the node you want to run,be processed by the method + public void RunNodeDependently(RuntimeNode runtimeNode,int dependencyLevel=0,bool processTargetNode=true){ var links = runtimeNode.InputLinks; foreach (var link in links){ var outputNode = RuntimeNodes[link.outPort.nodeDataId]; + if (outputNode is ConditionalRuntimeNode){ + continue; + } RunNodeDependently(outputNode,dependencyLevel+1); HandlingLink(link); } @@ -163,8 +266,13 @@ namespace TNode.TNodeCore.Runtime.Tools{ if (runtimeNode.OutputLinks.Count == 0 && dependencyLevel != 0){ return; } - runtimeNode.NodeData.Process(); + + if (processTargetNode||dependencyLevel != 0){ + runtimeNode.NodeData.Process(); + } + } + /// /// Max depth of dependency traversal,in case of some special situation. the dependency level bigger than this number will be considered as a loop. /// @@ -196,15 +304,7 @@ namespace TNode.TNodeCore.Runtime.Tools{ /// List of nodes you need to traversal to build graph tool /// Map stores the mapping of node data id to runtime node /// The graph you want to build graph tool for - public GraphTool(List list){ - CreateDummyRuntimeGraph(); - } - private void CreateDummyRuntimeGraph(){ - - - - } public GraphTool(IRuntimeNodeGraph graph){ RuntimeNodes = graph.GetRuntimeNodesDictionary();