From 7207424daf2e8cad8a99f6c11b0a8d9377e693b8 Mon Sep 17 00:00:00 2001
From: taoria <445625470@qq.com>
Date: Mon, 22 Aug 2022 17:53:12 +0800
Subject: [PATCH] feature:four method to iterate the node,making most type of
graph available.
---
TNodeCore/Runtime/Components/RuntimeGraph.cs | 24 ++-
.../RuntimeModels/IRuntimeNodeGraph.cs | 22 ++-
.../Runtime/RuntimeModels/StaticGraph.cs | 32 +++-
TNodeCore/Runtime/Tools/GraphTool.cs | 140 +++++++++++++++---
4 files changed, 183 insertions(+), 35 deletions(-)
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();