feature:add conditional graph

main
taoria 3 years ago
parent 1ae31eb057
commit 829ca3569f
  1. 92
      TNode/Samples/New HelloGraph.asset
  2. 3
      TNode/TNodeCore/Runtime/Attributes/Ports/InputAttribute.cs
  3. 4
      TNode/TNodeCore/Runtime/Attributes/Ports/OutputAttribute.cs
  4. 6
      TNode/TNodeCore/Runtime/Attributes/Ports/PortAttribute.cs
  5. 43
      TNode/TNodeCore/Runtime/Components/ConditionalGraph.cs
  6. 3
      TNode/TNodeCore/Runtime/Components/ConditionalGraph.cs.meta
  7. 2
      TNode/TNodeCore/Runtime/Components/RuntimeDataSaver.cs
  8. 163
      TNode/TNodeCore/Runtime/Components/RuntimeGraph.cs
  9. 34
      TNode/TNodeCore/Runtime/ConditionalRuntimeNode.cs
  10. 3
      TNode/TNodeCore/Runtime/ConditionalRuntimeNode.cs.meta
  11. 11
      TNode/TNodeCore/Runtime/Models/BlackboardDragNode.cs
  12. 0
      TNode/TNodeCore/Runtime/Models/BlackboardDragNode.cs.meta
  13. 9
      TNode/TNodeCore/Runtime/Models/ConditionalNode.cs
  14. 3
      TNode/TNodeCore/Runtime/Models/ConditionalNode.cs.meta
  15. 6
      TNode/TNodeCore/Runtime/Models/EntryNode.cs
  16. 3
      TNode/TNodeCore/Runtime/Models/EntryNode.cs.meta
  17. 11
      TNode/TNodeCore/Runtime/Models/SceneNode.cs
  18. 3
      TNode/TNodeCore/Runtime/Models/SceneNode.cs.meta
  19. 3
      TNode/TNodeCore/Runtime/RuntimeCache/IModelPropertyAccessor.cs
  20. 32
      TNode/TNodeCore/Runtime/RuntimeNode.cs
  21. 3
      TNode/TNodeCore/Runtime/Tools.meta
  22. 153
      TNode/TNodeCore/Runtime/Tools/GraphTool.cs
  23. 3
      TNode/TNodeCore/Runtime/Tools/GraphTool.cs.meta
  24. 6
      TNode/TNodeGraphViewImpl/Editor/Cache/NodeEditorExtensions.cs
  25. 6
      TNode/TNodeGraphViewImpl/Editor/Inspector/NodeInspectorInNode.cs
  26. 16
      TNode/TNodeGraphViewImpl/Editor/NodeGraphView/DataGraphView.cs
  27. 4
      TNode/TNodeGraphViewImpl/Editor/NodeViews/DragNodeView.cs
  28. 1
      TNode/TNodeGraphViewImpl/Editor/NodeViews/NodeView.cs

@ -14,74 +14,31 @@ MonoBehaviour:
m_EditorClassIdentifier:
nodeList:
- id: 0
- id: 1
- id: 2
nodeLinks:
- inPort:
portEntryName: A
nodeDataId: 3e72627f-af97-4056-b89c-04d4f2f127f5
outPort:
portEntryName: Value
nodeDataId: 4414d05b-da96-465e-a593-2e3dcfceaf23
- inPort:
portEntryName: B
nodeDataId: 3e72627f-af97-4056-b89c-04d4f2f127f5
outPort:
portEntryName: Value
nodeDataId: 1a4fd419-5584-4d43-a8c3-bebcad63a337
nodeLinks: []
blackboardData:
id: 3
id: 1
sceneReference:
editorModels:
- id: 4
- id: 2
- id: 3
graphViewModel:
id: 5
id: 4
references:
version: 1
00000000:
type: {class: BlackboardDragNodeData, ns: TNodeCore.Runtime.Models, asm: NewAssembly}
data:
positionInView:
serializedVersion: 2
x: 519
y: 361
width: 0
height: 0
id: 4414d05b-da96-465e-a593-2e3dcfceaf23
nodeName:
entryPoint: 0
isTest: 0
blackDragData: V3S.0
isListElement: 1
00000001:
type: {class: BlackboardDragNodeData, ns: TNodeCore.Runtime.Models, asm: NewAssembly}
data:
positionInView:
serializedVersion: 2
x: 519
y: 443
width: 0
height: 0
id: 1a4fd419-5584-4d43-a8c3-bebcad63a337
nodeName:
entryPoint: 0
isTest: 0
blackDragData: V2S.0
isListElement: 1
00000002:
type: {class: AddNode, ns: Samples, asm: Assembly-CSharp}
data:
positionInView:
serializedVersion: 2
x: 630
y: 361
x: 595
y: 315
width: 0
height: 0
id: 3e72627f-af97-4056-b89c-04d4f2f127f5
id: b2634d94-1655-4674-9b1c-b1c2e9ab3f6b
nodeName: AddNode
entryPoint: 0
isTest: 0
00000003:
00000001:
type: {class: HelloBlackboard, ns: TNode.Samples, asm: Assembly-CSharp}
data:
positionInView:
@ -93,11 +50,22 @@ MonoBehaviour:
id:
HelloString:
HelloGameObject: {fileID: 0}
V3S:
- {x: 0, y: 0, z: 0}
V2S:
- {x: 0, y: 0}
00000004:
V3S: []
V2S: []
00000002:
type: {class: PlacematModel, ns: TNode.TNodeCore.Editor.Models, asm: NewAssembly}
data:
positionInView:
serializedVersion: 2
x: 559
y: 197
width: 500
height: 500
id:
hostModels: []
zOrder: 0
title: Title
00000003:
type: {class: Comment, ns: TNode.TNodeCore.Editor.Models, asm: NewAssembly}
data:
positionInView:
@ -108,9 +76,9 @@ MonoBehaviour:
height: 0
id:
CommentedModel:
id: 2
CommentText:
00000005:
id: 0
CommentText: ddd
00000004:
type: {class: GraphViewModel, ns: TNode.TNodeCore.Editor.Models, asm: NewAssembly}
data:
positionInView:
@ -121,5 +89,5 @@ MonoBehaviour:
height: 0
id:
persistScale: 1
persistOffset: {x: -302, y: -93}
isBlackboardOn: 1
persistOffset: {x: 0, y: 0}
isBlackboardOn: 0

@ -8,7 +8,6 @@ namespace TNodeCore.Runtime.Attributes.Ports{
public class InputAttribute : PortAttribute{
public InputAttribute(string name="", PortNameHandling nameHandling = PortNameHandling.Auto,TypeHandling typeHandling=TypeHandling.Declared) : base(name, nameHandling,typeHandling){
}
public InputAttribute(Color color):base(color){
}
}
}

@ -6,9 +6,5 @@ namespace TNodeCore.Runtime.Attributes.Ports{
public OutputAttribute(string name="", PortNameHandling nameHandling = PortNameHandling.Auto,TypeHandling typeHandling = TypeHandling.Declared) : base(name, nameHandling,typeHandling){
}
public OutputAttribute(Color color):base(color){
}
}
}

@ -25,7 +25,7 @@ namespace TNodeCore.Runtime.Attributes.Ports{
public readonly PortNameHandling NameHandling;
public Type HandledType;
public bool Multiple = true;
public Color PortColor = Color.black;
public TypeHandling TypeHandling{ get; set; }
public PortAttribute(string name,PortNameHandling nameHandling=PortNameHandling.Auto,TypeHandling typeHandling=TypeHandling.Declared){
this.Name = name;
@ -33,10 +33,6 @@ namespace TNodeCore.Runtime.Attributes.Ports{
this.TypeHandling = typeHandling;
}
public PortAttribute(Color color):this("",PortNameHandling.Auto,TypeHandling.Declared){
PortColor = color;
}
}

@ -0,0 +1,43 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using TNodeCore.Runtime;
using TNodeCore.Runtime.Components;
using UnityEngine;
namespace TNode.TNodeCore.Runtime.Components{
public class ConditionalGraph:RuntimeGraph{
public ConditionalRuntimeNode EntryNode;
public ConditionalRuntimeNode CurrentNode{ get; set; }
public override void Build(){
base.Build();
var entry = GetRuntimeNodesOfType<EntryNode>();
if (entry.Count > 1){
Debug.LogError("There should be only one entry node in a conditional graph");
}
EntryNode = entry.FirstOrDefault() as ConditionalRuntimeNode;
}
public IEnumerator StepForward(){
CurrentNode = EntryNode;
while (CurrentNode != null){
//First let's process the node
CurrentNode.NodeData.Process();
//Then check if there are conditional transitions
var conditionalTransitions = CurrentNode.GetNextNodesId();
var transitionNode = new List<RuntimeNode>();
foreach (var conditionalTransition in conditionalTransitions){
transitionNode.Add(Get(conditionalTransition));
}
foreach (var runtimeNode in transitionNode){
if (runtimeNode is ConditionalRuntimeNode == false){
runtimeNode.Process();
}
}
CurrentNode = transitionNode.FirstOrDefault(x => x is ConditionalRuntimeNode) as ConditionalRuntimeNode;
yield return CurrentNode;
}
}
}
}

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4b83b17d30064dbeb53e41bce98ea195
timeCreated: 1660182748

@ -2,7 +2,7 @@
using System.IO;
using UnityEngine;
namespace TNodeCore.Runtime.Components{
namespace TNode.TNodeCore.Runtime.Components{
public class RuntimeDataSaver:MonoBehaviour{
public string saveName;
public string saveExtension = "tng";

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using TNode.TNodeCore.Runtime.Tools;
using TNodeCore.Runtime.Models;
using UnityEditor;
using UnityEngine;
@ -28,151 +29,7 @@ namespace TNodeCore.Runtime.Components{
/// <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>();
public RuntimeGraph Parent;
/// <summary>
/// Entry nodes of the graph. These are the nodes that has no input.
/// </summary>
[NonSerialized]
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;
foreach (var link in links){
HandlingLink(link);
}
node.NodeData.Process();
}
}
/// <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];
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());
}
/// <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 = 1145;
/// <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];
//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,RuntimeGraph graph){
RuntimeNodes = graphNodes;
Parent = graph;
if (list == null) return;
Queue<RuntimeNode> queue = new Queue<RuntimeNode>();
Dictionary<string,int> inDegreeCounterForTopologicalSort = new Dictionary<string, int>();
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);
EntryNodes.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]);
}
}
}
if(TopologicalOrder.Count!= list.Count){
throw new Exception("Topological sort failed,circular dependency detected");
}
inDegreeCounterForTopologicalSort.Clear();
queue.Clear();
}
}
/// <summary>
/// Holding the reference of the blackboard ,but it will be override by the runtime graph
/// </summary>
@ -187,7 +44,7 @@ namespace TNodeCore.Runtime.Components{
/// <summary>
/// Build the graph tool and other dependencies for the runtime graph
/// </summary>
public void Build(){
public virtual void Build(){
if (_build) return;
var link = graphData.NodeLinks;
@ -202,7 +59,7 @@ namespace TNodeCore.Runtime.Components{
}
var nodeList = RuntimeNodes.Values;
_graphTool = new GraphTool(nodeList.ToList(),RuntimeNodes,this);
var sceneNodes = RuntimeNodes.Values.Where(x => x.NodeData is SceneNodeData).Select(x => x.NodeData as SceneNodeData);
var sceneNodes = RuntimeNodes.Values.Where(x => x.NodeData is SceneNode).Select(x => x.NodeData as SceneNode);
foreach (var sceneNode in sceneNodes){
if (sceneNode != null) sceneNode.BlackboardData = runtimeBlackboardData;
}
@ -263,9 +120,9 @@ namespace TNodeCore.Runtime.Components{
}
#region build scene node data
#if UNITY_EDITOR
public void BuildSceneNodePersistentData(SceneNodeData sceneNodeData){
public void BuildSceneNodePersistentData(SceneNode sceneNode){
var persistentData = transform.Find("PersistentData").GetComponent<SceneDataPersistent>();
persistentData.SceneNodeDataDictionary.Add(sceneNodeData.id,sceneNodeData);
persistentData.SceneNodeDataDictionary.Add(sceneNode.id,sceneNode);
}
public SceneDataPersistent CreateSceneNodePersistentGameObject(){
@ -275,7 +132,7 @@ namespace TNodeCore.Runtime.Components{
}
public void BuildSceneNode(){
var fetchedSceneNode = graphData.NodeDictionary.Values.Where(x => x is SceneNodeData && x is BlackboardDragNodeData == false).ToArray();
var fetchedSceneNode = graphData.NodeDictionary.Values.Where(x => x is SceneNode && x is BlackboardDragNode == false).ToArray();
if (!fetchedSceneNode.Any()) return;
var scenePersistent = transform.Find("PersistentData")?.GetComponent<SceneDataPersistent>();
@ -288,7 +145,7 @@ namespace TNodeCore.Runtime.Components{
var sceneNodeData = scenePersistent.SceneNodeDataDictionary[nodeData.id];
RuntimeNodes[nodeData.id].NodeData = sceneNodeData;
}
else if (nodeData.Clone() is SceneNodeData clonedNodeData){
else if (nodeData.Clone() is SceneNode clonedNodeData){
clonedNodeData.BlackboardData = runtimeBlackboardData;
RuntimeNodes.Remove(nodeData.id);
RuntimeNodes.Add(nodeData.id,new RuntimeNode(clonedNodeData));
@ -303,7 +160,7 @@ namespace TNodeCore.Runtime.Components{
if (persistentData == null) return;
var fetchedSceneNode =
RuntimeNodes
.Where(x => x.Value.NodeData is SceneNodeData & x.Value.NodeData is BlackboardDragNodeData == false)
.Where(x => x.Value.NodeData is SceneNode & x.Value.NodeData is BlackboardDragNode == false)
.Select(x=>x.Value.NodeData).ToArray();
var dic = persistentData.SceneNodeDataDictionary;
@ -412,10 +269,10 @@ namespace TNodeCore.Runtime.Components{
public class SceneDataPersistent:MonoBehaviour,ISerializationCallbackReceiver{
[NonSerialized]
public readonly Dictionary<string,SceneNodeData> SceneNodeDataDictionary = new Dictionary<string,SceneNodeData>();
public readonly Dictionary<string,SceneNode> SceneNodeDataDictionary = new Dictionary<string,SceneNode>();
[SerializeReference]
public List<SceneNodeData> sceneNodeData=new List<SceneNodeData>();
public List<SceneNode> sceneNodeData=new List<SceneNode>();
public void OnBeforeSerialize(){

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using TNode.TNodeCore.Runtime.Models;
using TNodeCore.Runtime.Models;
using UnityEngine;
namespace TNodeCore.Runtime{
public class ConditionalRuntimeNode:RuntimeNode{
private readonly List<Tuple<string,Func<bool>>> PossibleTransition;
public ConditionalRuntimeNode(NodeData nodeData) : base(nodeData){
if (nodeData is ConditionalNode conditionalNode){
var transitionPort = GetPortsOfType<bool>();
PossibleTransition = new List<Tuple<string,Func<bool>>>();
foreach (var port in transitionPort){
if(GetPortDirection(port)==Direction.Input) continue;
PossibleTransition.Add(new Tuple<string, Func<bool>>(port,() => (bool)GetOutput(port)) );
}
}
else{
Debug.LogError("The Conditional runtime node must be applied on a Conditional node");
}
}
public string[] GetNextNodesId(){
var ports = PossibleTransition.Where(x => x.Item2());
var portNames = ports.Select(x => x.Item1);
//Search output links to found the link contains portNames as outport's name
var outputLinks = OutputLink.Where(x => portNames.Contains(x.outPort.portEntryName));
return outputLinks.Select(x => x.inPort.nodeDataId).ToArray();
}
}
}

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4464b577bdf34f3686cd46fa9dedde5d
timeCreated: 1660184384

@ -7,7 +7,7 @@ using UnityEngine;
namespace TNodeCore.Runtime.Models{
[Serializable]
[InternalUsage]
public class BlackboardDragNodeData:SceneNodeData{
public class BlackboardDragNode:SceneNode{
public string BlackDragData{
get => blackDragData;
set{
@ -40,16 +40,9 @@ namespace TNodeCore.Runtime.Models{
}
public bool isListElement=false;
public BlackboardDragNodeData(){
public BlackboardDragNode(){
}
}
[Serializable]
public class SceneNodeData:NodeData{
public BlackboardData BlackboardData{ get; set; }
}
}

@ -0,0 +1,9 @@
using TNodeCore.Runtime.Attributes.Ports;
using TNodeCore.Runtime.Models;
namespace TNode.TNodeCore.Runtime.Models{
public class ConditionalNode:NodeData{
[Input]
public bool In{ get; set; }
}
}

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: dd1951ffbe7d4ea0a821916fdd13b123
timeCreated: 1660184416

@ -0,0 +1,6 @@
using TNodeCore.Runtime.Models;
namespace TNode.TNodeCore.Runtime.Components{
public abstract class EntryNode:NodeData{
}
}

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8493b3f8aab445b388621fd55fb89f31
timeCreated: 1660182851

@ -0,0 +1,11 @@
using System;
namespace TNodeCore.Runtime.Models{
[Serializable]
public class SceneNode:NodeData{
public BlackboardData BlackboardData{ get; set; }
}
}

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 50d89f16f3674b0a9f0dbb57c893b9ff
timeCreated: 1660182925

@ -1,4 +1,5 @@
using System;
using UnityEditor.Experimental.GraphView;
namespace TNodeCore.Runtime.RuntimeCache{
public interface IModelPropertyAccessor{
@ -8,5 +9,7 @@ namespace TNodeCore.Runtime.RuntimeCache{
public Type Type{ get; set; }
}
}

@ -2,10 +2,9 @@
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Codice.Client.Common.TreeGrouper;
using TNodeCore.Runtime.Attributes.Ports;
using TNodeCore.Runtime.Models;
using TNodeCore.Runtime.RuntimeCache;
using UnityEngine;
namespace TNodeCore.Runtime{
public class RuntimeNode{
@ -30,10 +29,30 @@ namespace TNodeCore.Runtime{
}
}
public object GetOutput(string portName){
return _portAccessors[portName].GetValue(NodeData);
}
public string[] GetPortsOfType<T> (){
var ports = new List<string>();
foreach (var port in _portAccessors.Keys){
if(_portAccessors[port].Type==typeof(T)){
ports.Add(port);
}
}
return ports.ToArray();
}
/// <summary>
/// Call it carefully to cache
/// </summary>
/// <param name="portName"></param>
/// <returns></returns>
public Direction GetPortDirection(string portName){
var attribute = NodeData.GetType().GetField(portName).GetCustomAttribute<PortAttribute>();
if (attribute is InputAttribute){
return Direction.Input;
}
return Direction.Output;
}
private readonly Dictionary<string, IModelPropertyAccessor> _portAccessors;
public Action Process;
@ -43,10 +62,7 @@ namespace TNodeCore.Runtime{
//Caching the type of the node
_type = nodeData.GetType();
var info = nodeData.GetType().GetProperties();
_portAccessors = RuntimeCache.RuntimeCache.Instance.CachedPropertyAccessors[_type];
}
public List<string> GetInputNodesId(){
List<string> dependencies = new List<string>();
@ -58,4 +74,8 @@ namespace TNodeCore.Runtime{
}
}
public enum Direction{
Input,
Output
}
}

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 81fec5ad21ea4855a80174c1dd79641a
timeCreated: 1660182663

@ -0,0 +1,153 @@
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{
/// <summary>
/// Topological order of the graph nodes
/// </summary>
[NonSerialized]
public readonly List<RuntimeNode> TopologicalOrder = new List<RuntimeNode>();
public RuntimeGraph Parent;
public bool TopologicalSorted = false;
/// <summary>
/// Entry nodes of the graph. These are the nodes that has no input.
/// </summary>
[NonSerialized]
public readonly List<RuntimeNode> NonDependencyNode = 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;
foreach (var link in links){
HandlingLink(link);
}
node.NodeData.Process();
}
}
/// <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];
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());
}
/// <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 = 1145;
/// <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];
//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,RuntimeGraph graph){
RuntimeNodes = graphNodes;
Parent = graph;
if (list == null) return;
Queue<RuntimeNode> queue = new Queue<RuntimeNode>();
Dictionary<string,int> inDegreeCounterForTopologicalSort = new Dictionary<string, int>();
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();
}
}
}

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 22cadb92b33e4bbcbf31921f28b4dc6c
timeCreated: 1660182638

@ -234,13 +234,13 @@ namespace TNode.TNodeGraphViewImpl.Editor.Cache{
//Check the generic type of BaseNodeView by t
if (t.IsGenericType){
//AKA if BlackboardDragNodeData<Camera> is pulled
//Get BlackboardDragNodeData<T> as generic type
//AKA if BlackboardDragNode<Camera> is pulled
//Get BlackboardDragNode<T> as generic type
var genericTypeDefinition = t.GetGenericTypeDefinition();
//What you want is a BaseNodeView<BlackboardDragNodeData<T>> to be created
//What you want is a BaseNodeView<BlackboardDragNode<T>> to be created
var genericViewType = typeof(BaseNodeView<>).MakeGenericType(genericTypeDefinition);
//search for the specific type of genericViewType in the dictionary

@ -38,8 +38,8 @@ namespace TNode.TNodeGraphViewImpl.Editor.Inspector{
private void RefreshPropertyDrawer(){
//Check if the model's type is a generic type of BlackboardDragNodeData<>
if (_data.GetType().IsSubclassOf(typeof(BlackboardDragNodeData))){
//Check if the model's type is a generic type of BlackboardDragNode<>
if (_data.GetType().IsSubclassOf(typeof(BlackboardDragNode))){
return;
}
var serializedObject = new SerializedObject((NodeDataWrapper)_data);
@ -55,7 +55,7 @@ namespace TNode.TNodeGraphViewImpl.Editor.Inspector{
serializedObject.ApplyModifiedProperties();
((NodeDataWrapper)_data).ForceNotify();
});
if (_data is SceneNodeData && _data is BlackboardDragNodeData==false){
if (_data is SceneNode && _data is BlackboardDragNode==false){
}
else{

@ -324,7 +324,7 @@ namespace TNode.TNodeGraphViewImpl.Editor.NodeGraphView{
text = "Run Once"
};
runButton.RegisterCallback<ClickEvent>(evt => {
Debug.Log(IsRuntimeGraph);
if (IsRuntimeGraph){
_runtimeGraph.TraverseAll();
@ -363,8 +363,8 @@ namespace TNode.TNodeGraphViewImpl.Editor.NodeGraphView{
var blackboardFields = data.OfType<BlackboardField >();
foreach (var selectable in blackboardFields){
if(selectable is { } field) {
//Make a constructor of BlackboardDragNodeData<field.PropertyType > by reflection
var dragNodeData = NodeCreator.InstantiateNodeData<BlackboardDragNodeData>();
//Make a constructor of BlackboardDragNode<field.PropertyType > by reflection
var dragNodeData = NodeCreator.InstantiateNodeData<BlackboardDragNode>();
dragNodeData.BlackboardData = GetBlackboardData();
dragNodeData.BlackDragData = field.BlackboardProperty.PropertyName;
AddTNode(dragNodeData,new Rect(realPos,new Vector2(200,200)));
@ -374,8 +374,8 @@ namespace TNode.TNodeGraphViewImpl.Editor.NodeGraphView{
var blackboardEntries = data.OfType<BlackboardDataEntry>();
foreach (var selectable in blackboardEntries){
if(selectable is { } entry) {
//Make a constructor of BlackboardDragNodeData<field.PropertyType > by reflection
var dragNodeData = NodeCreator.InstantiateNodeData<BlackboardDragNodeData>();
//Make a constructor of BlackboardDragNode<field.PropertyType > by reflection
var dragNodeData = NodeCreator.InstantiateNodeData<BlackboardDragNode>();
dragNodeData.BlackboardData = GetBlackboardData();
dragNodeData.BlackDragData = entry.propertyPath;
AddTNode(dragNodeData,new Rect(realPos,new Vector2(200,200)));
@ -416,14 +416,14 @@ namespace TNode.TNodeGraphViewImpl.Editor.NodeGraphView{
if(dataNode==null)
continue;
//Get the derived type of NodeAttribute View from the node type
if (dataNode is SceneNodeData runtimeNodeData){
if (runtimeNodeData is BlackboardDragNodeData){
if (dataNode is SceneNode runtimeNodeData){
if (runtimeNodeData is BlackboardDragNode){
runtimeNodeData.BlackboardData = GetBlackboardData();
AddPersistentNode(runtimeNodeData);
}
else{
var node = _runtimeGraph.Get(runtimeNodeData.id).NodeData as SceneNodeData;
var node = _runtimeGraph.Get(runtimeNodeData.id).NodeData as SceneNode;
AddPersistentNode(node);
}
}

@ -9,14 +9,14 @@ using UnityEngine.UIElements;
namespace TNode.TNodeGraphViewImpl.Editor.NodeViews{
[ViewComponent]
public class DragBaseNodeView:BaseNodeView<BlackboardDragNodeData>{
public class DragBaseNodeView:BaseNodeView<BlackboardDragNode>{
public DragBaseNodeView() : base(){
this.titleContainer.visible = false;
this.titleContainer.RemoveFromHierarchy();
this.OnDataChanged += OnDataChangedHandler;
}
private void OnDataChangedHandler(BlackboardDragNodeData obj){
private void OnDataChangedHandler(BlackboardDragNode obj){
var port = this.Q<Port>();
var label = port.Q<Label>();
var blackboard = obj.BlackboardData;

@ -13,6 +13,7 @@ using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;
using Direction = UnityEditor.Experimental.GraphView.Direction;
namespace TNode.TNodeGraphViewImpl.Editor.NodeViews{

Loading…
Cancel
Save