From 85a684569e934a33a0654ff8ecc5157c990c0733 Mon Sep 17 00:00:00 2001 From: taoria <445625470@qq.com> Date: Sat, 16 Jul 2022 19:53:08 +0800 Subject: [PATCH] feat:allowing runtime node traversal with non circular graph --- TNodeCore/Attribute/RuntimeNodeAttribute.cs | 6 + .../Attribute/RuntimeNodeAttribute.cs.meta | 3 + TNodeCore/Models/BlackboardData.cs | 5 +- TNodeCore/Models/NodeData.cs | 12 +- TNodeCore/Models/NodeLink.cs | 2 + TNodeCore/Runtime/RuntimeGraph.cs | 140 +++++++++++++++++- TNodeCore/Runtime/RuntimeNode.cs | 39 ++++- TNodeCore/RuntimeCache/RuntimeCache.cs | 69 +++++++-- .../Editor/Cache/NodeEditorExtensions.cs | 3 + .../Editor/Inspector/NodeInspectorInNode.cs | 14 ++ .../Editor/NodeGraphView/DataGraphView.cs | 2 +- 11 files changed, 266 insertions(+), 29 deletions(-) create mode 100644 TNodeCore/Attribute/RuntimeNodeAttribute.cs create mode 100644 TNodeCore/Attribute/RuntimeNodeAttribute.cs.meta diff --git a/TNodeCore/Attribute/RuntimeNodeAttribute.cs b/TNodeCore/Attribute/RuntimeNodeAttribute.cs new file mode 100644 index 0000000..6d2ad80 --- /dev/null +++ b/TNodeCore/Attribute/RuntimeNodeAttribute.cs @@ -0,0 +1,6 @@ +using JetBrains.Annotations; +using TNodeCore.Models; + +namespace TNodeCore.Attribute{ + +} \ No newline at end of file diff --git a/TNodeCore/Attribute/RuntimeNodeAttribute.cs.meta b/TNodeCore/Attribute/RuntimeNodeAttribute.cs.meta new file mode 100644 index 0000000..ec78521 --- /dev/null +++ b/TNodeCore/Attribute/RuntimeNodeAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e489ca10c5734869be9bce6e1a18297e +timeCreated: 1657952959 \ No newline at end of file diff --git a/TNodeCore/Models/BlackboardData.cs b/TNodeCore/Models/BlackboardData.cs index 95b35b6..1da25ce 100644 --- a/TNodeCore/Models/BlackboardData.cs +++ b/TNodeCore/Models/BlackboardData.cs @@ -6,6 +6,9 @@ namespace TNodeCore.Models{ /// [Serializable] - public class BlackboardData:IModel{ + public class BlackboardData:IModel,ICloneable{ + public object Clone(){ + return this.MemberwiseClone(); + } } } \ No newline at end of file diff --git a/TNodeCore/Models/NodeData.cs b/TNodeCore/Models/NodeData.cs index 79aeb10..3e1565a 100644 --- a/TNodeCore/Models/NodeData.cs +++ b/TNodeCore/Models/NodeData.cs @@ -1,5 +1,6 @@ using System; using TNodeCore.Attribute; +using UnityEngine; namespace TNodeCore.Models{ /// @@ -22,12 +23,15 @@ namespace TNodeCore.Models{ public bool entryPoint; - public virtual void OnProcess(){ + public virtual void Process(){ } -// #if UNITY_EDITOR -// public Rect rect; -// #endif +#if UNITY_EDITOR + [HideInInspector] public bool isTest; + public virtual void OnTest(){ + + } + #endif } } \ No newline at end of file diff --git a/TNodeCore/Models/NodeLink.cs b/TNodeCore/Models/NodeLink.cs index dfccbcc..1b929ac 100644 --- a/TNodeCore/Models/NodeLink.cs +++ b/TNodeCore/Models/NodeLink.cs @@ -7,9 +7,11 @@ namespace TNodeCore.Models{ // public DialogueNodePortData From{ get; } public PortInfo inPort; public PortInfo outPort; + public NodeLink(PortInfo inPort, PortInfo outPort){ this.inPort = inPort; this.outPort = outPort; } + } } \ No newline at end of file diff --git a/TNodeCore/Runtime/RuntimeGraph.cs b/TNodeCore/Runtime/RuntimeGraph.cs index 9df5282..06cae28 100644 --- a/TNodeCore/Runtime/RuntimeGraph.cs +++ b/TNodeCore/Runtime/RuntimeGraph.cs @@ -1,20 +1,152 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using TNodeCore.Models; using UnityEngine; namespace TNodeCore.Runtime{ public class RuntimeGraph:MonoBehaviour{ public GraphData graphData; - public SortedSet _sortedSet; + + public readonly Dictionary RuntimeNodes = new Dictionary(); + + private GraphTool _graphTool; + + private class GraphTool{ + [NonSerialized] + public readonly List TopologicalOrder = new List(); + public readonly List EntryNodes = new List(); + public readonly Dictionary RuntimeNodes; + public void DependencyTraversal(RuntimeNode runtimeNode){ + var links = runtimeNode.InputLink; + foreach (var link in links){ + var outputNode = RuntimeNodes[link.outPort.nodeDataId]; + DependencyTraversal(outputNode); + HandlingLink(link); + } + runtimeNode.NodeData.Process(); + } + + public void HandlingLink(NodeLink nodeLink){ + 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.portName); + inNode.SetInput(nodeLink.inPort.portName, outValue); + } + public GraphTool(List list, Dictionary graphNodes){ + RuntimeNodes = graphNodes; + 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"); + } + RuntimeNodes.Clear(); + inDegreeCounterForTopologicalSort.Clear(); + queue.Clear(); + } + + + } [SerializeReference] public BlackboardData runtimeBlackboardData; + + private bool _build = false; + public void Build(){ + + var link = graphData.NodeLinks; + //iterate links and create runtime nodes + foreach (var linkData in link){ + ModifyOrCreateInNode(linkData); + ModifyOrCreateOutNode(linkData); + } + var nodeList = RuntimeNodes.Values; + _graphTool = new GraphTool(nodeList.ToList(),RuntimeNodes); + _build = true; + } + + public RuntimeNode Get(NodeData nodeData){ + if(!_build) + Build(); + + if(RuntimeNodes.ContainsKey(nodeData.id)){ + return RuntimeNodes[nodeData.id]; + } + return null; + } + + public RuntimeNode Get(string id){ + if (RuntimeNodes.ContainsKey(id)){ + return RuntimeNodes[id]; + } + + return null; + } + //DFS search for resolving dependency + public void StartDependencyTraversal(NodeData startNode,NodeData currentNode,int level=0){ + if (!_build) + Build(); + if(_graphTool==null) + return; + _graphTool.DependencyTraversal(Get(startNode)); + var inputNodesId = Get(currentNode).GetInputNodesId(); + foreach (var s in inputNodesId){ + var runtimeNode = Get(s); + } + } + private void ModifyOrCreateInNode(NodeLink linkData){ + var inNodeId = linkData.inPort.nodeDataId; + var inNode = graphData.NodeDictionary[inNodeId]; + if (!RuntimeNodes.ContainsKey(inNode.id)){ + var runtimeInNode = new RuntimeNode(inNode); + RuntimeNodes.Add(inNode.id,runtimeInNode); + } + RuntimeNodes[inNode.id].InputLink.Add(linkData); + + } + private void ModifyOrCreateOutNode(NodeLink linkData){ + var outNodeId = linkData.outPort.nodeDataId; + var outNode = graphData.NodeDictionary[outNodeId]; + if(!RuntimeNodes.ContainsKey(outNode.id)){ + var runtimeOutNode = new RuntimeNode(outNode); + RuntimeNodes.Add(outNode.id,runtimeOutNode); + } + RuntimeNodes[outNode.id].OutputLink.Add(linkData); + } public void OnValidate(){ if(runtimeBlackboardData==null||runtimeBlackboardData.GetType()==typeof(BlackboardData)){ - if(graphData!=null) - runtimeBlackboardData = RuntimeCache.RuntimeCache.Instance.GetBlackboardData(graphData); + if (graphData != null) + runtimeBlackboardData = graphData.blackboardData.Clone() as BlackboardData; } } + + } public enum ProcessingStrategy{ diff --git a/TNodeCore/Runtime/RuntimeNode.cs b/TNodeCore/Runtime/RuntimeNode.cs index 428b763..b83e15a 100644 --- a/TNodeCore/Runtime/RuntimeNode.cs +++ b/TNodeCore/Runtime/RuntimeNode.cs @@ -1,12 +1,39 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using Codice.Client.Common.TreeGrouper; using TNodeCore.Models; +using TNodeCore.RuntimeCache; namespace TNodeCore.Runtime{ - public abstract class RuntimeNode{ - public NodeData NodeData; - public List NodeLinks; - public void ProcessThisNode(){ - NodeData.OnProcess(); + public class RuntimeNode{ + public NodeData NodeData { get; set; } + //the link connect to node's in port + public List InputLink; + //the link connect to node's out port + public List OutputLink; + public Type type; + + + public void SetInput(string portName,object value){ + NodeData.SetValue(portName, value); + } + public object GetOutput(string portName){ + return NodeData.GetValue(portName); + } + + public RuntimeNode(NodeData nodeData){ + NodeData = nodeData; + //Caching the type of the node + type = nodeData.GetType(); + } + public List GetInputNodesId(){ + List dependencies = new List(); + foreach (NodeLink link in InputLink) + { + dependencies.Add(link.outPort.nodeDataId); + } + return dependencies; } + } } \ No newline at end of file diff --git a/TNodeCore/RuntimeCache/RuntimeCache.cs b/TNodeCore/RuntimeCache/RuntimeCache.cs index 6afe225..d3a1a79 100644 --- a/TNodeCore/RuntimeCache/RuntimeCache.cs +++ b/TNodeCore/RuntimeCache/RuntimeCache.cs @@ -50,6 +50,11 @@ namespace TNodeCore.RuntimeCache{ AddBlackboardDataTypeToCache(type,attribute); RegisterRuntimeBlackboard(type); } + //Check if the type is a node data type + if(typeof(NodeData).IsAssignableFrom(type)){ + //if it is, add it to the cache + RegisterRuntimeNodeData(type); + } } @@ -101,6 +106,41 @@ namespace TNodeCore.RuntimeCache{ } } + public void RegisterRuntimeNodeData(Type type){ + if (type == null) return; + if(!CachedDelegatesForGettingValue.ContainsKey(type)){ + CachedDelegatesForGettingValue.Add(type, new Dictionary()); + CachedDelegatesForSettingValue.Add(type,new Dictionary()); + var properties = type.GetProperties(); + foreach(var property in properties){ + //if the property only has a setter ,skip + + if(property.SetMethod != null){ + var setValueDelegate = SetValueDelegateForProperty(property); + CachedDelegatesForSettingValue[type].Add(property.Name,setValueDelegate); + } + if(property.GetMethod != null){ + var getValueDelegate = GetValueDelegateForProperty(property); + CachedDelegatesForGettingValue[type].Add(property.Name,getValueDelegate); + } + + + + } + //register the fields + var fields = type.GetFields(); + foreach(var field in fields){ + + var getValueDelegate = GetValueDelegateForField(field); + CachedDelegatesForGettingValue[type].Add(field.Name,getValueDelegate); + if (field.IsPublic){ + var setValueDelegate = SetValueDelegateForField(field); + CachedDelegatesForSettingValue[type].Add(field.Name,setValueDelegate); + } + + } + } + } private GetValueDelegate GetValueDelegateForField(FieldInfo field){ return field.GetValue; } @@ -120,26 +160,29 @@ namespace TNodeCore.RuntimeCache{ public static class RuntimeExtension{ //todo latter on i will try some way caching reflection more efficiently - public static T GetValue(this BlackboardData blackboardData,string path){ - var method = RuntimeCache.Instance.CachedDelegatesForGettingValue[blackboardData.GetType()][path]; - return (T) method.Invoke(blackboardData); + public static T GetValue(this IModel data,string path,Type type=null){ + var method = RuntimeCache.Instance.CachedDelegatesForGettingValue[type??data.GetType()][path]; + return (T) method.Invoke(data); } - public static object GetValue(this BlackboardData blackboardData, string path){ - var method = RuntimeCache.Instance.CachedDelegatesForGettingValue[blackboardData.GetType()][path]; - return method.Invoke(blackboardData); + public static object GetValue(this IModel data, string path,Type type=null){ + var method = RuntimeCache.Instance.CachedDelegatesForGettingValue[type??data.GetType()][path]; + return method.Invoke(data); } - public static void SetValue(this BlackboardData blackboardData,string path,T value){ - var method = RuntimeCache.Instance.CachedDelegatesForSettingValue[blackboardData.GetType()][path]; - method.Invoke(blackboardData,value); + + public static void SetValue(this IModel data,string path,T value,Type type=null){ + var method = RuntimeCache.Instance.CachedDelegatesForSettingValue[type??data.GetType()][path]; + method.Invoke(data,value); } - public static void SetValue(this BlackboardData blackboardData,string path,object value){ - var method = RuntimeCache.Instance.CachedDelegatesForSettingValue[blackboardData.GetType()][path]; - method.Invoke(blackboardData,value); + public static void SetValue(this IModel data,string path,object value,Type type=null){ + var method = RuntimeCache.Instance.CachedDelegatesForSettingValue[type??data.GetType()][path]; + method.Invoke(data,value); } - public static RuntimeCache.GetValueDelegate GetValueDelegate(this BlackboardData blackboardData,string path){ + public static RuntimeCache.GetValueDelegate GetValueDelegate(this IModel blackboardData,string path){ var method = RuntimeCache.Instance.CachedDelegatesForGettingValue[blackboardData.GetType()][path]; return method; } + + } } \ No newline at end of file diff --git a/TNodeGraphViewImpl/Editor/Cache/NodeEditorExtensions.cs b/TNodeGraphViewImpl/Editor/Cache/NodeEditorExtensions.cs index 68460b3..5d55934 100644 --- a/TNodeGraphViewImpl/Editor/Cache/NodeEditorExtensions.cs +++ b/TNodeGraphViewImpl/Editor/Cache/NodeEditorExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using TNode.Editor; using TNode.Editor.NodeViews; using TNodeCore.Attribute; @@ -58,10 +59,12 @@ namespace TNodeGraphViewImpl.Editor.Cache{ SetViewComponentAttribute(type); //Register Node Data by GraphUsageAttribute. SetGraphUsageAttribute(type); + } } } } + private void SetGraphUsageAttribute(Type type){ foreach (var attribute in type.GetCustomAttributes(typeof(GraphUsageAttribute), true)){ diff --git a/TNodeGraphViewImpl/Editor/Inspector/NodeInspectorInNode.cs b/TNodeGraphViewImpl/Editor/Inspector/NodeInspectorInNode.cs index 4660f6a..8d44a8c 100644 --- a/TNodeGraphViewImpl/Editor/Inspector/NodeInspectorInNode.cs +++ b/TNodeGraphViewImpl/Editor/Inspector/NodeInspectorInNode.cs @@ -36,6 +36,9 @@ namespace TNode.Editor.Inspector{ RefreshPropertyDrawer(); } + private void CreateTestButton(){ + + } private void RefreshPropertyDrawer(){ //Check if the data's type is a generic type of BlackboardDragNodeData<> if (_data.GetType().IsSubclassOf(typeof(BlackboardDragNodeData))){ @@ -51,6 +54,17 @@ namespace TNode.Editor.Inspector{ var drawer = new PropertyField(serializedObject.FindProperty("data").FindPropertyRelative(field.Name),field.Name); drawer.Bind(serializedObject); Add(drawer); + + + } + if (_data.isTest){ + //Add a test button for the node + var testButton = new Button(()=>{ + Debug.Log("Test button clicked"); + }); + testButton.text = "Test"; + _data.OnTest(); + Add(testButton); } } diff --git a/TNodeGraphViewImpl/Editor/NodeGraphView/DataGraphView.cs b/TNodeGraphViewImpl/Editor/NodeGraphView/DataGraphView.cs index d2713d5..e23b499 100644 --- a/TNodeGraphViewImpl/Editor/NodeGraphView/DataGraphView.cs +++ b/TNodeGraphViewImpl/Editor/NodeGraphView/DataGraphView.cs @@ -376,7 +376,7 @@ namespace TNodeGraphViewImpl.Editor.NodeGraphView{ public override List GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter){ - return ports.Where(x => x.portType == startPort.portType).ToList(); + return ports.Where(x => x.portType == startPort.portType || x.portType.IsAssignableFrom(startPort.portType)).ToList(); } public virtual void OnGraphViewCreate(){