feature:four method to iterate the node,making most type of graph available.

main
taoria 3 years ago
parent 346bd84d8b
commit 7207424daf
  1. 24
      TNodeCore/Runtime/Components/RuntimeGraph.cs
  2. 22
      TNodeCore/Runtime/RuntimeModels/IRuntimeNodeGraph.cs
  3. 32
      TNodeCore/Runtime/RuntimeModels/StaticGraph.cs
  4. 140
      TNodeCore/Runtime/Tools/GraphTool.cs

@ -70,7 +70,7 @@ namespace TNodeCore.Runtime.Components{
#if UNITY_EDITOR #if UNITY_EDITOR
BuildSceneNode(); BuildSceneNode();
#endif #endif
_runtimeNodeEnumerator = _graphTool.BreathFirstSearch(); ResetState();
_build = true; _build = true;
} }
@ -171,7 +171,21 @@ namespace TNodeCore.Runtime.Components{
} }
public void ResetState(){ 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(){ public NodeData CurrentNode(){
@ -268,6 +282,8 @@ namespace TNodeCore.Runtime.Components{
_graphTool.DirectlyTraversal(); _graphTool.DirectlyTraversal();
} }
public AccessMethod AccessMethod{ get; set; } = AccessMethod.Dependency;
public RuntimeNode GetRuntimeNode(NodeData nodeData){ public RuntimeNode GetRuntimeNode(NodeData nodeData){
if(!_build) if(!_build)
Build(); Build();
@ -333,8 +349,4 @@ namespace TNodeCore.Runtime.Components{
} }
} }
public enum ProcessingStrategy{
BreadthFirst,
DepthFirst
}
} }

@ -3,13 +3,33 @@ using System.Collections.Generic;
using TNodeCore.Runtime.Models; using TNodeCore.Runtime.Models;
namespace TNodeCore.Runtime.RuntimeModels{ 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,
/// <summary>
/// Iterate all nodes by a topological order
/// </summary>
Dependency,
}
public interface IRuntimeNodeGraph{ public interface IRuntimeNodeGraph{
public AccessMethod AccessMethod{ get; set; }
public RuntimeNode GetRuntimeNode(NodeData nodeData); public RuntimeNode GetRuntimeNode(NodeData nodeData);
public RuntimeNode GetRuntimeNode(string id); public RuntimeNode GetRuntimeNode(string id);
public BlackboardData GetBlackboardData(); public BlackboardData GetBlackboardData();
public List<RuntimeNode> GetRuntimeNodes(); public List<RuntimeNode> GetRuntimeNodes();
public Dictionary<string,RuntimeNode> GetRuntimeNodesDictionary(); public Dictionary<string,RuntimeNode> GetRuntimeNodesDictionary();
public NodeData GetNode(string id); public NodeData GetNode(string id);
List<RuntimeNode> GetRuntimeNodesOfType(Type type); List<RuntimeNode> GetRuntimeNodesOfType(Type type);
List<RuntimeNode> GetRuntimeNodesOfType<T>(); List<RuntimeNode> GetRuntimeNodesOfType<T>();

@ -8,7 +8,7 @@ using TNodeCore.Runtime.Models;
namespace TNodeCore.Runtime.RuntimeModels{ namespace TNodeCore.Runtime.RuntimeModels{
public class StaticGraph:IRuntimeNodeGraph{ public class StaticGraph:IRuntimeNodeGraph{
private Dictionary<string,RuntimeNode> _nodes; private Dictionary<string,RuntimeNode> _nodes;
private IEnumerator<RuntimeNode> _breathFirstEnumerator; private IEnumerator<RuntimeNode> _runtimeNodeEnumerator;
private readonly GraphTool _graphTool; private readonly GraphTool _graphTool;
private readonly GraphData _originalData; private readonly GraphData _originalData;
@ -47,13 +47,29 @@ namespace TNodeCore.Runtime.RuntimeModels{
ModifyLinks(link); ModifyLinks(link);
} }
_graphTool = new GraphTool(this); _graphTool = new GraphTool(this);
_breathFirstEnumerator = _graphTool.BreathFirstSearch(); _runtimeNodeEnumerator = _graphTool.BreathFirstSearch();
} }
public void ResetState(){ 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){ public RuntimeNode GetRuntimeNode(NodeData nodeData){
return _nodes[nodeData.id]; return _nodes[nodeData.id];
} }
@ -92,15 +108,15 @@ namespace TNodeCore.Runtime.RuntimeModels{
} }
public RuntimeNode MoveNext(){ public RuntimeNode MoveNext(){
_breathFirstEnumerator.MoveNext(); _runtimeNodeEnumerator.MoveNext();
return _breathFirstEnumerator.Current; return _runtimeNodeEnumerator.Current;
} }
public RuntimeNode CurrentRuntimeNode(){ public RuntimeNode CurrentRuntimeNode(){
if (_breathFirstEnumerator.Current == null){ if (_runtimeNodeEnumerator.Current == null){
_breathFirstEnumerator.MoveNext(); _runtimeNodeEnumerator.MoveNext();
} }
return _breathFirstEnumerator.Current; return _runtimeNodeEnumerator.Current;
} }
} }

@ -1,6 +1,9 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using TNode.TNodeCore.Runtime.Components;
using TNodeCore.Runtime; using TNodeCore.Runtime;
using TNodeCore.Runtime.Components; using TNodeCore.Runtime.Components;
using TNodeCore.Runtime.Extensions; using TNodeCore.Runtime.Extensions;
@ -50,68 +53,164 @@ namespace TNode.TNodeCore.Runtime.Tools{
} }
node.NodeData.Process(); node.NodeData.Process();
} }
}
//A IEnumerator version of the DirectlyTraversal,used to run the graph in a coroutine or somewhere you need
public IEnumerator<RuntimeNode> 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;
}
}
/// <summary>
/// usually used in state transition
/// </summary>
/// <returns></returns>
public IEnumerator<RuntimeNode> 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. //Try to enable state transition from node to node.
public IEnumerator<RuntimeNode> DeepFirstSearchWithCondition(){ public IEnumerator<RuntimeNode> DeepFirstSearchWithCondition(){
//Define the basic data structure for a traversal of the graph
Stack<RuntimeNode> stack = new Stack<RuntimeNode>(); Stack<RuntimeNode> stack = new Stack<RuntimeNode>();
HashSet<RuntimeNode> alreadyContained = new HashSet<RuntimeNode>();
HashSet<RuntimeNode> visited = new HashSet<RuntimeNode>();
foreach (var runtimeNode in NonDependencyNode){ foreach (var runtimeNode in NonDependencyNode){
stack.Push(runtimeNode); stack.Push(runtimeNode);
} }
while (stack.Count > 0){ while (stack.Count > 0){
var node = stack.Pop(); var node = stack.Pop();
visited.Add(node);
if (node is ConditionalRuntimeNode conditionalRuntimeNode){ if (node is ConditionalRuntimeNode conditionalRuntimeNode){
var ids = conditionalRuntimeNode.GetConditionalNextIds(); var ids = conditionalRuntimeNode.GetConditionalNextIds();
var nextNodes = ids.Select(id=>RuntimeNodes[id]).ToList(); var nextNodes = ids.Select(id=>RuntimeNodes[id]).ToList();
foreach (var runtimeNode in nextNodes){ foreach (var runtimeNode in nextNodes){
stack.Push(runtimeNode); AddToCollectionIfMeetCondition(alreadyContained, visited,runtimeNode, stack);
} }
} }
else{ else{
var nextNodes = node.OutputLinks.Select(link => RuntimeNodes[link.inPort.nodeDataId]); foreach (var runtimeNode in node.OutputLinks.Select(link => RuntimeNodes[link.inPort.nodeDataId])){
foreach (var runtimeNode in nextNodes){ AddToCollectionIfMeetCondition(alreadyContained, visited,runtimeNode, stack);
stack.Push(runtimeNode);
} }
node.OutputLinks.ForEach(HandlingLink);
} }
node.OutputLinks.ForEach(HandlingLink);
node.NodeData.Process(); node.NodeData.Process();
yield return node; yield return node;
} }
} }
/// <summary>
/// Breath first search for the graph.Not a standard BFS algorithm since all entries will be executed first.
/// </summary>
/// <returns>The IEnumerator to iterate the node</returns>
public IEnumerator<RuntimeNode> BreathFirstSearch(){ public IEnumerator<RuntimeNode> BreathFirstSearch(){
//Define the basic data structure for a traversal of the graph
Queue<RuntimeNode> queue = new Queue<RuntimeNode>(); Queue<RuntimeNode> queue = new Queue<RuntimeNode>();
//Already contained method to avoid duplicate traversal
HashSet<RuntimeNode> alreadyContained = new HashSet<RuntimeNode>(); HashSet<RuntimeNode> alreadyContained = new HashSet<RuntimeNode>();
//Visited method to avoid duplicate traversal
HashSet<RuntimeNode> visited = new HashSet<RuntimeNode>(); HashSet<RuntimeNode> visited = new HashSet<RuntimeNode>();
//Firstly add all entry node to the queue
foreach (var runtimeNode in NonDependencyNode){ foreach (var runtimeNode in NonDependencyNode){
queue.Enqueue(runtimeNode); queue.Enqueue(runtimeNode);
alreadyContained.Add(runtimeNode); alreadyContained.Add(runtimeNode);
} }
//Iterate the queue to implement bfs
while (queue.Count > 0){ while (queue.Count > 0){
var node = queue.Dequeue(); var node = queue.Dequeue();
visited.Add(node); 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){ if (node is ConditionalRuntimeNode conditionalRuntimeNode){
var ids = conditionalRuntimeNode.GetConditionalNextIds(); var ids = conditionalRuntimeNode.GetConditionalNextIds();
var nextNodes = ids.Select(id=>RuntimeNodes[id]).ToList(); var nextNodes = ids.Select(id=>RuntimeNodes[id]).ToList();
foreach (var runtimeNode in nextNodes){ foreach (var runtimeNode in nextNodes){
AddNodeToQueueIfMeetCondition(alreadyContained, runtimeNode, queue); AddToCollectionIfMeetCondition(alreadyContained, visited,runtimeNode, queue);
} }
} }
else{ else{
foreach (var runtimeNode in node.OutputLinks.Select(link => RuntimeNodes[link.inPort.nodeDataId])){ 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(); node.NodeData.Process();
//Handle the links of the node
node.OutputLinks.ForEach(HandlingLink);
yield return node; yield return node;
} }
} }
private void AddNodeToQueueIfMeetCondition(HashSet<RuntimeNode> alreadyContained, RuntimeNode runtimeNode, Queue<RuntimeNode> queue){ private void AddToCollectionIfMeetCondition(HashSet<RuntimeNode> alreadyContained,HashSet<RuntimeNode> visited, RuntimeNode runtimeNode, Queue<RuntimeNode> 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<RuntimeNode> alreadyContained,HashSet<RuntimeNode> visited, RuntimeNode runtimeNode, Stack<RuntimeNode> 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<RuntimeNode> alreadyContained, RuntimeNode runtimeNode, Stack<RuntimeNode> stack){
//Check if the node is already contained in the queue //Check if the node is already contained in the queue
if (alreadyContained.Contains(runtimeNode)) return; if (alreadyContained.Contains(runtimeNode)) return;
@ -123,7 +222,7 @@ namespace TNode.TNodeCore.Runtime.Tools{
if (allDependenciesVisited == false) return; if (allDependenciesVisited == false) return;
//If all conditions are met, add the node to the queue //If all conditions are met, add the node to the queue
queue.Enqueue(runtimeNode); stack.Push(runtimeNode);
alreadyContained.Add(runtimeNode); alreadyContained.Add(runtimeNode);
} }
@ -146,10 +245,14 @@ namespace TNode.TNodeCore.Runtime.Tools{
/// </summary> /// </summary>
/// <param name="runtimeNode">The node you want to resolve dependency</param> /// <param name="runtimeNode">The node you want to resolve dependency</param>
/// <param name="dependencyLevel">search depth,no need provide a number when use outside</param> /// <param name="dependencyLevel">search depth,no need provide a number when use outside</param>
public void RunNodeDependently(RuntimeNode runtimeNode,int dependencyLevel=0){ /// <param name="processTargetNode">if the the node of the 0 level should be processed,which is the node you want to run,be processed by the method</param>
public void RunNodeDependently(RuntimeNode runtimeNode,int dependencyLevel=0,bool processTargetNode=true){
var links = runtimeNode.InputLinks; var links = runtimeNode.InputLinks;
foreach (var link in links){ foreach (var link in links){
var outputNode = RuntimeNodes[link.outPort.nodeDataId]; var outputNode = RuntimeNodes[link.outPort.nodeDataId];
if (outputNode is ConditionalRuntimeNode){
continue;
}
RunNodeDependently(outputNode,dependencyLevel+1); RunNodeDependently(outputNode,dependencyLevel+1);
HandlingLink(link); HandlingLink(link);
} }
@ -163,8 +266,13 @@ namespace TNode.TNodeCore.Runtime.Tools{
if (runtimeNode.OutputLinks.Count == 0 && dependencyLevel != 0){ if (runtimeNode.OutputLinks.Count == 0 && dependencyLevel != 0){
return; return;
} }
runtimeNode.NodeData.Process();
if (processTargetNode||dependencyLevel != 0){
runtimeNode.NodeData.Process();
}
} }
/// <summary> /// <summary>
/// Max depth of dependency traversal,in case of some special situation. the dependency level bigger than this number will be considered as a loop. /// Max depth of dependency traversal,in case of some special situation. the dependency level bigger than this number will be considered as a loop.
/// </summary> /// </summary>
@ -196,15 +304,7 @@ namespace TNode.TNodeCore.Runtime.Tools{
/// <param name="list">List of nodes you need to traversal to build graph tool</param> /// <param name="list">List of nodes you need to traversal to build graph tool</param>
/// <param name="graphNodes">Map stores the mapping of node data id to runtime node</param> /// <param name="graphNodes">Map stores the mapping of node data id to runtime node</param>
/// <param name="graph">The graph you want to build graph tool for</param> /// <param name="graph">The graph you want to build graph tool for</param>
public GraphTool(List<NodeData> list){
CreateDummyRuntimeGraph();
}
private void CreateDummyRuntimeGraph(){
}
public GraphTool(IRuntimeNodeGraph graph){ public GraphTool(IRuntimeNodeGraph graph){
RuntimeNodes = graph.GetRuntimeNodesDictionary(); RuntimeNodes = graph.GetRuntimeNodesDictionary();

Loading…
Cancel
Save