@ -7,20 +7,53 @@ using UnityEngine;
namespace TNodeCore.Components {
public class RuntimeGraph : MonoBehaviour {
/// <summary>
/// Graph data reference to be used in runtime
/// </summary>
public GraphData graphData ;
/// <summary>
/// Runtime copy of scene node data to hold references to scene objects
/// </summary>
public List < SceneNodeData > sceneNodes ;
/// <summary>
/// Map of node id to runtime node
/// </summary>
public readonly Dictionary < string , RuntimeNode > RuntimeNodes = new Dictionary < string , RuntimeNode > ( ) ;
///<summary>
/// The graph tool the current runtime graph is using
/// </summary>
private GraphTool _ graphTool ;
/// <summary>
/// Inner graph tool to help with graph operations
/// </summary>
private class GraphTool {
/// <summary>
/// Topological order of the graph nodes
/// </summary>
[NonSerialized]
public readonly List < RuntimeNode > TopologicalOrder = new List < RuntimeNode > ( ) ;
/// <summary>
/// Entry nodes of the graph. These are the nodes that has no input.
/// </summary>
public readonly List < RuntimeNode > EntryNodes = new List < RuntimeNode > ( ) ;
/// <summary>
/// Cached data for Dependency traversal.
/// </summary>
public readonly Dictionary < string , object > OutputCached = new Dictionary < string , object > ( ) ;
/// <summary>
/// Ssed to detect if the graph tool is caching the output data of the node
/// </summary>
private bool _ isCachingOutput = false ;
/// <summary>
/// elements are read only ,do not modify them
/// </summary>
public readonly Dictionary < string , RuntimeNode > 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 ;
@ -30,26 +63,76 @@ namespace TNodeCore.Components{
node . NodeData . Process ( ) ;
}
}
public void DependencyTraversal ( RuntimeNode runtimeNode ) {
/// <summary>
/// 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.
/// </summary>
public void StartCachingPort ( ) {
_ isCachingOutput = true ;
}
public void EndCachingPort ( ) {
_ isCachingOutput = false ;
OutputCached . Clear ( ) ;
}
/// <summary>
/// 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.
/// </summary>
/// <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>
public void RunNodeDependently ( RuntimeNode runtimeNode , int dependencyLevel = 0 ) {
var links = runtimeNode . InputLink ;
foreach ( var link in links ) {
var outputNode = RuntimeNodes [ link . outPort . nodeDataId ] ;
DependencyTraversal ( outputNode ) ;
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 ( ) ;
}
/// <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.
/// </summary>
private const int DependencyLevelMax = 1 1 4 5 ;
/// <summary>
/// Handling a node link to transfer data from it's output side to the input side
/// </summary>
/// <param name="nodeLink">Link you want to process</param>
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 ] ;
//out node is node output data
//in node is node receive data
var outValue = outNode . GetOutput ( nodeLink . outPort . portEntryName ) ;
//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 ) ;
}
/// <summary>
/// Constructor of the graph tool,it will traverse the graph and build the topological order of the graph.
/// </summary>
/// <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>
public GraphTool ( List < RuntimeNode > list , Dictionary < string , RuntimeNode > graphNodes ) {
RuntimeNodes = graphNodes ;
if ( list = = null ) return ;
@ -87,10 +170,20 @@ namespace TNodeCore.Components{
}
/// <summary>
/// Holding the reference of the blackboard ,but it will be override by the runtime graph
/// </summary>
[SerializeReference]
public BlackboardData runtimeBlackboardData ;
/// <summary>
/// Check if the runtime graph is build .a built graph has a graph tool set up
/// </summary>
[NonSerialized]
private bool _ build = false ;
/// <summary>
/// Build the graph tool and other dependencies for the runtime graph
/// </summary>
public void Build ( ) {
var link = graphData . NodeLinks ;
@ -109,6 +202,11 @@ namespace TNodeCore.Components{
_ build = true ;
}
/// <summary>
/// Cast the node data to a runtime node
/// </summary>
/// <param name="nodeData">Node data you provided</param>
/// <returns></returns>
public RuntimeNode Get ( NodeData nodeData ) {
if ( ! _ build )
Build ( ) ;
@ -117,20 +215,24 @@ namespace TNodeCore.Components{
}
return null ;
}
/// <summary>
/// Get the runtime node from an id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public RuntimeNode Get ( string id ) {
if ( RuntimeNodes . ContainsKey ( id ) ) {
return RuntimeNodes [ id ] ;
}
return null ;
}
//DFS search for resolving dependency
public bool Resolve Dependency ( NodeData startNode ) {
//DFS search to run a node.
public bool RunOn Dependency ( NodeData startNode ) {
if ( ! _ build )
Build ( ) ;
if ( _ graphTool = = null )
return false ;
_ graphTool . DependencyTraversal ( Get ( startNode ) ) ;
_ graphTool . RunNodeDependently ( Get ( startNode ) ) ;
return true ;
}
public bool ResolveDependency ( ) {
@ -152,10 +254,20 @@ namespace TNodeCore.Components{
}
public List < RuntimeNode > GetRuntimeNodesOfType < T > ( ) {
return RuntimeNodes . Values . Where ( x = > x . NodeType = = typeof ( T ) ) . ToList ( ) ;
return RuntimeNodes . Values . Where ( x = > typeof ( T ) . IsAssignableFrom ( x . NodeType ) ) . ToList ( ) ;
}
public List < RuntimeNode > GetRuntimeNodesOfType ( Type type ) {
return RuntimeNodes . Values . Where ( x = > x . NodeType = = type ) . ToList ( ) ;
return RuntimeNodes . Values . Where ( x = > type . IsAssignableFrom ( type ) ) . ToList ( ) ;
}
public void RunNodesOfType ( Type t ) {
var nodes = GetRuntimeNodesOfType ( t ) ;
_ graphTool . StartCachingPort ( ) ;
foreach ( var runtimeNode in nodes ) {
RunOnDependency ( runtimeNode . NodeData ) ;
}
_ graphTool . EndCachingPort ( ) ;
}
private void ModifyOrCreateOutNode ( NodeLink linkData ) {
var outNodeId = linkData . outPort . nodeDataId ;
@ -170,7 +282,7 @@ namespace TNodeCore.Components{
public void OnValidate ( ) {
if ( runtimeBlackboardData = = null | | runtimeBlackboardData . GetType ( ) = = typeof ( BlackboardData ) ) {
if ( graphData ! = null )
runtimeBlackboardData = graphData . blackboardData . Clone ( ) as BlackboardData ;
runtimeBlackboardData = graphData . blackboardData ? . Clone ( ) as BlackboardData ;
}
}
@ -186,6 +298,7 @@ namespace TNodeCore.Components{
public void Start ( ) {
Build ( ) ;
}
public virtual void RuntimeExecute ( ) {
_ graphTool . DirectlyTraversal ( ) ;