From 829ca3569f23f26a3484a19b8d850c2464bc1315 Mon Sep 17 00:00:00 2001 From: taoria <445625470@qq.com> Date: Thu, 11 Aug 2022 10:53:52 +0800 Subject: [PATCH] feature:add conditional graph --- TNode/Samples/New HelloGraph.asset | 92 ++++------ .../Attributes/Ports/InputAttribute.cs | 3 +- .../Attributes/Ports/OutputAttribute.cs | 4 - .../Runtime/Attributes/Ports/PortAttribute.cs | 6 +- .../Runtime/Components/ConditionalGraph.cs | 43 +++++ .../Components/ConditionalGraph.cs.meta | 3 + .../Runtime/Components/RuntimeDataSaver.cs | 2 +- .../Runtime/Components/RuntimeGraph.cs | 163 ++---------------- .../Runtime/ConditionalRuntimeNode.cs | 34 ++++ .../Runtime/ConditionalRuntimeNode.cs.meta | 3 + ...dDragNodeData.cs => BlackboardDragNode.cs} | 11 +- ...ata.cs.meta => BlackboardDragNode.cs.meta} | 0 .../Runtime/Models/ConditionalNode.cs | 9 + .../Runtime/Models/ConditionalNode.cs.meta | 3 + TNode/TNodeCore/Runtime/Models/EntryNode.cs | 6 + .../Runtime/Models/EntryNode.cs.meta | 3 + TNode/TNodeCore/Runtime/Models/SceneNode.cs | 11 ++ .../Runtime/Models/SceneNode.cs.meta | 3 + .../RuntimeCache/IModelPropertyAccessor.cs | 3 + TNode/TNodeCore/Runtime/RuntimeNode.cs | 32 +++- TNode/TNodeCore/Runtime/Tools.meta | 3 + TNode/TNodeCore/Runtime/Tools/GraphTool.cs | 153 ++++++++++++++++ .../TNodeCore/Runtime/Tools/GraphTool.cs.meta | 3 + .../Editor/Cache/NodeEditorExtensions.cs | 6 +- .../Editor/Inspector/NodeInspectorInNode.cs | 6 +- .../Editor/NodeGraphView/DataGraphView.cs | 16 +- .../Editor/NodeViews/DragNodeView.cs | 4 +- .../Editor/NodeViews/NodeView.cs | 1 + 28 files changed, 368 insertions(+), 258 deletions(-) create mode 100644 TNode/TNodeCore/Runtime/Components/ConditionalGraph.cs create mode 100644 TNode/TNodeCore/Runtime/Components/ConditionalGraph.cs.meta create mode 100644 TNode/TNodeCore/Runtime/ConditionalRuntimeNode.cs create mode 100644 TNode/TNodeCore/Runtime/ConditionalRuntimeNode.cs.meta rename TNode/TNodeCore/Runtime/Models/{BlackboardDragNodeData.cs => BlackboardDragNode.cs} (84%) rename TNode/TNodeCore/Runtime/Models/{BlackboardDragNodeData.cs.meta => BlackboardDragNode.cs.meta} (100%) create mode 100644 TNode/TNodeCore/Runtime/Models/ConditionalNode.cs create mode 100644 TNode/TNodeCore/Runtime/Models/ConditionalNode.cs.meta create mode 100644 TNode/TNodeCore/Runtime/Models/EntryNode.cs create mode 100644 TNode/TNodeCore/Runtime/Models/EntryNode.cs.meta create mode 100644 TNode/TNodeCore/Runtime/Models/SceneNode.cs create mode 100644 TNode/TNodeCore/Runtime/Models/SceneNode.cs.meta create mode 100644 TNode/TNodeCore/Runtime/Tools.meta create mode 100644 TNode/TNodeCore/Runtime/Tools/GraphTool.cs create mode 100644 TNode/TNodeCore/Runtime/Tools/GraphTool.cs.meta diff --git a/TNode/Samples/New HelloGraph.asset b/TNode/Samples/New HelloGraph.asset index 0ea0f1e..6b3e3d1 100644 --- a/TNode/Samples/New HelloGraph.asset +++ b/TNode/Samples/New HelloGraph.asset @@ -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 diff --git a/TNode/TNodeCore/Runtime/Attributes/Ports/InputAttribute.cs b/TNode/TNodeCore/Runtime/Attributes/Ports/InputAttribute.cs index 676f6db..583db63 100644 --- a/TNode/TNodeCore/Runtime/Attributes/Ports/InputAttribute.cs +++ b/TNode/TNodeCore/Runtime/Attributes/Ports/InputAttribute.cs @@ -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){ - } + } } \ No newline at end of file diff --git a/TNode/TNodeCore/Runtime/Attributes/Ports/OutputAttribute.cs b/TNode/TNodeCore/Runtime/Attributes/Ports/OutputAttribute.cs index 47eb6c9..eb76fa4 100644 --- a/TNode/TNodeCore/Runtime/Attributes/Ports/OutputAttribute.cs +++ b/TNode/TNodeCore/Runtime/Attributes/Ports/OutputAttribute.cs @@ -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){ - } - - } } \ No newline at end of file diff --git a/TNode/TNodeCore/Runtime/Attributes/Ports/PortAttribute.cs b/TNode/TNodeCore/Runtime/Attributes/Ports/PortAttribute.cs index 9146a79..cb6f4ba 100644 --- a/TNode/TNodeCore/Runtime/Attributes/Ports/PortAttribute.cs +++ b/TNode/TNodeCore/Runtime/Attributes/Ports/PortAttribute.cs @@ -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; - } } diff --git a/TNode/TNodeCore/Runtime/Components/ConditionalGraph.cs b/TNode/TNodeCore/Runtime/Components/ConditionalGraph.cs new file mode 100644 index 0000000..8419a4f --- /dev/null +++ b/TNode/TNodeCore/Runtime/Components/ConditionalGraph.cs @@ -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(); + 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(); + + 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; + } + } + } +} \ No newline at end of file diff --git a/TNode/TNodeCore/Runtime/Components/ConditionalGraph.cs.meta b/TNode/TNodeCore/Runtime/Components/ConditionalGraph.cs.meta new file mode 100644 index 0000000..e62b167 --- /dev/null +++ b/TNode/TNodeCore/Runtime/Components/ConditionalGraph.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4b83b17d30064dbeb53e41bce98ea195 +timeCreated: 1660182748 \ No newline at end of file diff --git a/TNode/TNodeCore/Runtime/Components/RuntimeDataSaver.cs b/TNode/TNodeCore/Runtime/Components/RuntimeDataSaver.cs index c437395..0dbda32 100644 --- a/TNode/TNodeCore/Runtime/Components/RuntimeDataSaver.cs +++ b/TNode/TNodeCore/Runtime/Components/RuntimeDataSaver.cs @@ -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"; diff --git a/TNode/TNodeCore/Runtime/Components/RuntimeGraph.cs b/TNode/TNodeCore/Runtime/Components/RuntimeGraph.cs index da99a2d..9311e86 100644 --- a/TNode/TNodeCore/Runtime/Components/RuntimeGraph.cs +++ b/TNode/TNodeCore/Runtime/Components/RuntimeGraph.cs @@ -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{ /// /// Inner graph tool to help with graph operations /// - private class GraphTool{ - - /// - /// Topological order of the graph nodes - /// - [NonSerialized] - public readonly List TopologicalOrder = new List(); - - public RuntimeGraph Parent; - - /// - /// Entry nodes of the graph. These are the nodes that has no input. - /// - [NonSerialized] - public readonly List EntryNodes = new List(); - /// - /// Cached data for Dependency traversal. - /// - public readonly Dictionary OutputCached = new Dictionary(); - - /// - /// Ssed to detect if the graph tool is caching the output data of the node - /// - private bool _isCachingOutput = false; - /// - /// elements are read only ,do not modify them - /// - public readonly Dictionary 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(); - } - } - /// - /// 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. - /// - public void StartCachingPort(){ - _isCachingOutput = true; - } - public void EndCachingPort(){ - _isCachingOutput = false; - OutputCached.Clear(); - } - /// - /// 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. - /// - /// 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){ - 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()); - } - /// - /// Max depth of dependency traversal,in case of some special situation. the dependency level bigger than this number will be considered as a loop. - /// - private const int DependencyLevelMax = 1145; - /// - /// Handling a node link to transfer data from it's output side to the input side - /// - /// Link you want to process - 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); - } - /// - /// Constructor of the graph tool,it will traverse the graph and build the topological order of the graph. - /// - /// List of nodes you need to traversal to build graph tool - /// Map stores the mapping of node data id to runtime node - - public GraphTool(List list, Dictionary graphNodes,RuntimeGraph graph){ - RuntimeNodes = graphNodes; - Parent = graph; - if (list == null) return; - Queue queue = new Queue(); - Dictionary inDegreeCounterForTopologicalSort = new Dictionary(); - 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(); - } - - - } /// /// Holding the reference of the blackboard ,but it will be override by the runtime graph /// @@ -187,7 +44,7 @@ namespace TNodeCore.Runtime.Components{ /// /// Build the graph tool and other dependencies for the runtime graph /// - 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(); - 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(); @@ -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 SceneNodeDataDictionary = new Dictionary(); + public readonly Dictionary SceneNodeDataDictionary = new Dictionary(); [SerializeReference] - public List sceneNodeData=new List(); + public List sceneNodeData=new List(); public void OnBeforeSerialize(){ diff --git a/TNode/TNodeCore/Runtime/ConditionalRuntimeNode.cs b/TNode/TNodeCore/Runtime/ConditionalRuntimeNode.cs new file mode 100644 index 0000000..4b8c8f9 --- /dev/null +++ b/TNode/TNodeCore/Runtime/ConditionalRuntimeNode.cs @@ -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>> PossibleTransition; + public ConditionalRuntimeNode(NodeData nodeData) : base(nodeData){ + if (nodeData is ConditionalNode conditionalNode){ + var transitionPort = GetPortsOfType(); + PossibleTransition = new List>>(); + foreach (var port in transitionPort){ + if(GetPortDirection(port)==Direction.Input) continue; + PossibleTransition.Add(new Tuple>(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(); + } + + } +} \ No newline at end of file diff --git a/TNode/TNodeCore/Runtime/ConditionalRuntimeNode.cs.meta b/TNode/TNodeCore/Runtime/ConditionalRuntimeNode.cs.meta new file mode 100644 index 0000000..a0b83c4 --- /dev/null +++ b/TNode/TNodeCore/Runtime/ConditionalRuntimeNode.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4464b577bdf34f3686cd46fa9dedde5d +timeCreated: 1660184384 \ No newline at end of file diff --git a/TNode/TNodeCore/Runtime/Models/BlackboardDragNodeData.cs b/TNode/TNodeCore/Runtime/Models/BlackboardDragNode.cs similarity index 84% rename from TNode/TNodeCore/Runtime/Models/BlackboardDragNodeData.cs rename to TNode/TNodeCore/Runtime/Models/BlackboardDragNode.cs index fa32032..c28dcb0 100644 --- a/TNode/TNodeCore/Runtime/Models/BlackboardDragNodeData.cs +++ b/TNode/TNodeCore/Runtime/Models/BlackboardDragNode.cs @@ -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; } - - } } \ No newline at end of file diff --git a/TNode/TNodeCore/Runtime/Models/BlackboardDragNodeData.cs.meta b/TNode/TNodeCore/Runtime/Models/BlackboardDragNode.cs.meta similarity index 100% rename from TNode/TNodeCore/Runtime/Models/BlackboardDragNodeData.cs.meta rename to TNode/TNodeCore/Runtime/Models/BlackboardDragNode.cs.meta diff --git a/TNode/TNodeCore/Runtime/Models/ConditionalNode.cs b/TNode/TNodeCore/Runtime/Models/ConditionalNode.cs new file mode 100644 index 0000000..4fe96f4 --- /dev/null +++ b/TNode/TNodeCore/Runtime/Models/ConditionalNode.cs @@ -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; } + } +} \ No newline at end of file diff --git a/TNode/TNodeCore/Runtime/Models/ConditionalNode.cs.meta b/TNode/TNodeCore/Runtime/Models/ConditionalNode.cs.meta new file mode 100644 index 0000000..ed7c4ee --- /dev/null +++ b/TNode/TNodeCore/Runtime/Models/ConditionalNode.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: dd1951ffbe7d4ea0a821916fdd13b123 +timeCreated: 1660184416 \ No newline at end of file diff --git a/TNode/TNodeCore/Runtime/Models/EntryNode.cs b/TNode/TNodeCore/Runtime/Models/EntryNode.cs new file mode 100644 index 0000000..317d3d8 --- /dev/null +++ b/TNode/TNodeCore/Runtime/Models/EntryNode.cs @@ -0,0 +1,6 @@ +using TNodeCore.Runtime.Models; + +namespace TNode.TNodeCore.Runtime.Components{ + public abstract class EntryNode:NodeData{ + } +} \ No newline at end of file diff --git a/TNode/TNodeCore/Runtime/Models/EntryNode.cs.meta b/TNode/TNodeCore/Runtime/Models/EntryNode.cs.meta new file mode 100644 index 0000000..257be3a --- /dev/null +++ b/TNode/TNodeCore/Runtime/Models/EntryNode.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8493b3f8aab445b388621fd55fb89f31 +timeCreated: 1660182851 \ No newline at end of file diff --git a/TNode/TNodeCore/Runtime/Models/SceneNode.cs b/TNode/TNodeCore/Runtime/Models/SceneNode.cs new file mode 100644 index 0000000..8de2a6f --- /dev/null +++ b/TNode/TNodeCore/Runtime/Models/SceneNode.cs @@ -0,0 +1,11 @@ +using System; + +namespace TNodeCore.Runtime.Models{ + [Serializable] + public class SceneNode:NodeData{ + + + public BlackboardData BlackboardData{ get; set; } + + } +} \ No newline at end of file diff --git a/TNode/TNodeCore/Runtime/Models/SceneNode.cs.meta b/TNode/TNodeCore/Runtime/Models/SceneNode.cs.meta new file mode 100644 index 0000000..63b685c --- /dev/null +++ b/TNode/TNodeCore/Runtime/Models/SceneNode.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 50d89f16f3674b0a9f0dbb57c893b9ff +timeCreated: 1660182925 \ No newline at end of file diff --git a/TNode/TNodeCore/Runtime/RuntimeCache/IModelPropertyAccessor.cs b/TNode/TNodeCore/Runtime/RuntimeCache/IModelPropertyAccessor.cs index c263ffe..65030a4 100644 --- a/TNode/TNodeCore/Runtime/RuntimeCache/IModelPropertyAccessor.cs +++ b/TNode/TNodeCore/Runtime/RuntimeCache/IModelPropertyAccessor.cs @@ -1,4 +1,5 @@ using System; +using UnityEditor.Experimental.GraphView; namespace TNodeCore.Runtime.RuntimeCache{ public interface IModelPropertyAccessor{ @@ -6,6 +7,8 @@ namespace TNodeCore.Runtime.RuntimeCache{ void SetValue(object model, object value); public Type Type{ get; set; } + + } diff --git a/TNode/TNodeCore/Runtime/RuntimeNode.cs b/TNode/TNodeCore/Runtime/RuntimeNode.cs index 2bf09f7..713eb00 100644 --- a/TNode/TNodeCore/Runtime/RuntimeNode.cs +++ b/TNode/TNodeCore/Runtime/RuntimeNode.cs @@ -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 (){ + var ports = new List(); + foreach (var port in _portAccessors.Keys){ + if(_portAccessors[port].Type==typeof(T)){ + ports.Add(port); + } + } + return ports.ToArray(); + } + /// + /// Call it carefully to cache + /// + /// + /// + public Direction GetPortDirection(string portName){ + var attribute = NodeData.GetType().GetField(portName).GetCustomAttribute(); + if (attribute is InputAttribute){ + return Direction.Input; + } + return Direction.Output; + } private readonly Dictionary _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 GetInputNodesId(){ List dependencies = new List(); @@ -58,4 +74,8 @@ namespace TNodeCore.Runtime{ } } + public enum Direction{ + Input, + Output + } } \ No newline at end of file diff --git a/TNode/TNodeCore/Runtime/Tools.meta b/TNode/TNodeCore/Runtime/Tools.meta new file mode 100644 index 0000000..cf9dfd1 --- /dev/null +++ b/TNode/TNodeCore/Runtime/Tools.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 81fec5ad21ea4855a80174c1dd79641a +timeCreated: 1660182663 \ No newline at end of file diff --git a/TNode/TNodeCore/Runtime/Tools/GraphTool.cs b/TNode/TNodeCore/Runtime/Tools/GraphTool.cs new file mode 100644 index 0000000..c5f9537 --- /dev/null +++ b/TNode/TNodeCore/Runtime/Tools/GraphTool.cs @@ -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{ + + /// + /// Topological order of the graph nodes + /// + [NonSerialized] + public readonly List TopologicalOrder = new List(); + + public RuntimeGraph Parent; + public bool TopologicalSorted = false; + + /// + /// Entry nodes of the graph. These are the nodes that has no input. + /// + [NonSerialized] + public readonly List NonDependencyNode = new List(); + /// + /// Cached data for Dependency traversal. + /// + public readonly Dictionary OutputCached = new Dictionary(); + + /// + /// Ssed to detect if the graph tool is caching the output data of the node + /// + private bool _isCachingOutput = false; + /// + /// elements are read only ,do not modify them + /// + public readonly Dictionary 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(); + } + } + + /// + /// 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. + /// + public void StartCachingPort(){ + _isCachingOutput = true; + } + public void EndCachingPort(){ + _isCachingOutput = false; + OutputCached.Clear(); + } + /// + /// 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. + /// + /// 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){ + 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()); + } + /// + /// Max depth of dependency traversal,in case of some special situation. the dependency level bigger than this number will be considered as a loop. + /// + private const int DependencyLevelMax = 1145; + /// + /// Handling a node link to transfer data from it's output side to the input side + /// + /// Link you want to process + 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); + } + /// + /// Constructor of the graph tool,it will traverse the graph and build the topological order of the graph. + /// + /// List of nodes you need to traversal to build graph tool + /// Map stores the mapping of node data id to runtime node + + public GraphTool(List list, Dictionary graphNodes,RuntimeGraph graph){ + RuntimeNodes = graphNodes; + Parent = graph; + if (list == null) return; + Queue queue = new Queue(); + Dictionary inDegreeCounterForTopologicalSort = new Dictionary(); + 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(); + } + + + } +} \ No newline at end of file diff --git a/TNode/TNodeCore/Runtime/Tools/GraphTool.cs.meta b/TNode/TNodeCore/Runtime/Tools/GraphTool.cs.meta new file mode 100644 index 0000000..b8dac32 --- /dev/null +++ b/TNode/TNodeCore/Runtime/Tools/GraphTool.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 22cadb92b33e4bbcbf31921f28b4dc6c +timeCreated: 1660182638 \ No newline at end of file diff --git a/TNode/TNodeGraphViewImpl/Editor/Cache/NodeEditorExtensions.cs b/TNode/TNodeGraphViewImpl/Editor/Cache/NodeEditorExtensions.cs index b32188a..827cd51 100644 --- a/TNode/TNodeGraphViewImpl/Editor/Cache/NodeEditorExtensions.cs +++ b/TNode/TNodeGraphViewImpl/Editor/Cache/NodeEditorExtensions.cs @@ -234,13 +234,13 @@ namespace TNode.TNodeGraphViewImpl.Editor.Cache{ //Check the generic type of BaseNodeView by t if (t.IsGenericType){ - //AKA if BlackboardDragNodeData is pulled - //Get BlackboardDragNodeData as generic type + //AKA if BlackboardDragNode is pulled + //Get BlackboardDragNode as generic type var genericTypeDefinition = t.GetGenericTypeDefinition(); - //What you want is a BaseNodeView> to be created + //What you want is a BaseNodeView> to be created var genericViewType = typeof(BaseNodeView<>).MakeGenericType(genericTypeDefinition); //search for the specific type of genericViewType in the dictionary diff --git a/TNode/TNodeGraphViewImpl/Editor/Inspector/NodeInspectorInNode.cs b/TNode/TNodeGraphViewImpl/Editor/Inspector/NodeInspectorInNode.cs index a7195e7..38047c2 100644 --- a/TNode/TNodeGraphViewImpl/Editor/Inspector/NodeInspectorInNode.cs +++ b/TNode/TNodeGraphViewImpl/Editor/Inspector/NodeInspectorInNode.cs @@ -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{ diff --git a/TNode/TNodeGraphViewImpl/Editor/NodeGraphView/DataGraphView.cs b/TNode/TNodeGraphViewImpl/Editor/NodeGraphView/DataGraphView.cs index d374c97..2afd375 100644 --- a/TNode/TNodeGraphViewImpl/Editor/NodeGraphView/DataGraphView.cs +++ b/TNode/TNodeGraphViewImpl/Editor/NodeGraphView/DataGraphView.cs @@ -324,7 +324,7 @@ namespace TNode.TNodeGraphViewImpl.Editor.NodeGraphView{ text = "Run Once" }; runButton.RegisterCallback(evt => { - Debug.Log(IsRuntimeGraph); + if (IsRuntimeGraph){ _runtimeGraph.TraverseAll(); @@ -363,8 +363,8 @@ namespace TNode.TNodeGraphViewImpl.Editor.NodeGraphView{ var blackboardFields = data.OfType(); foreach (var selectable in blackboardFields){ if(selectable is { } field) { - //Make a constructor of BlackboardDragNodeData by reflection - var dragNodeData = NodeCreator.InstantiateNodeData(); + //Make a constructor of BlackboardDragNode by reflection + var dragNodeData = NodeCreator.InstantiateNodeData(); 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(); foreach (var selectable in blackboardEntries){ if(selectable is { } entry) { - //Make a constructor of BlackboardDragNodeData by reflection - var dragNodeData = NodeCreator.InstantiateNodeData(); + //Make a constructor of BlackboardDragNode by reflection + var dragNodeData = NodeCreator.InstantiateNodeData(); 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); } } diff --git a/TNode/TNodeGraphViewImpl/Editor/NodeViews/DragNodeView.cs b/TNode/TNodeGraphViewImpl/Editor/NodeViews/DragNodeView.cs index 030fac4..6748d03 100644 --- a/TNode/TNodeGraphViewImpl/Editor/NodeViews/DragNodeView.cs +++ b/TNode/TNodeGraphViewImpl/Editor/NodeViews/DragNodeView.cs @@ -9,14 +9,14 @@ using UnityEngine.UIElements; namespace TNode.TNodeGraphViewImpl.Editor.NodeViews{ [ViewComponent] - public class DragBaseNodeView:BaseNodeView{ + public class DragBaseNodeView:BaseNodeView{ 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(); var label = port.Q