feat:allowing runtime node traversal with non circular graph

main
taoria 3 years ago
parent 7e422bff29
commit 85a684569e
  1. 6
      TNodeCore/Attribute/RuntimeNodeAttribute.cs
  2. 3
      TNodeCore/Attribute/RuntimeNodeAttribute.cs.meta
  3. 5
      TNodeCore/Models/BlackboardData.cs
  4. 12
      TNodeCore/Models/NodeData.cs
  5. 2
      TNodeCore/Models/NodeLink.cs
  6. 140
      TNodeCore/Runtime/RuntimeGraph.cs
  7. 39
      TNodeCore/Runtime/RuntimeNode.cs
  8. 69
      TNodeCore/RuntimeCache/RuntimeCache.cs
  9. 3
      TNodeGraphViewImpl/Editor/Cache/NodeEditorExtensions.cs
  10. 14
      TNodeGraphViewImpl/Editor/Inspector/NodeInspectorInNode.cs
  11. 2
      TNodeGraphViewImpl/Editor/NodeGraphView/DataGraphView.cs

@ -0,0 +1,6 @@
using JetBrains.Annotations;
using TNodeCore.Models;
namespace TNodeCore.Attribute{
}

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e489ca10c5734869be9bce6e1a18297e
timeCreated: 1657952959

@ -6,6 +6,9 @@ namespace TNodeCore.Models{
/// </summary>
[Serializable]
public class BlackboardData:IModel{
public class BlackboardData:IModel,ICloneable{
public object Clone(){
return this.MemberwiseClone();
}
}
}

@ -1,5 +1,6 @@
using System;
using TNodeCore.Attribute;
using UnityEngine;
namespace TNodeCore.Models{
/// <summary>
@ -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
}
}

@ -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;
}
}
}

@ -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<RuntimeNode> _sortedSet;
public readonly Dictionary<string, RuntimeNode> RuntimeNodes = new Dictionary<string, RuntimeNode>();
private GraphTool _graphTool;
private class GraphTool{
[NonSerialized]
public readonly List<RuntimeNode> TopologicalOrder = new List<RuntimeNode>();
public readonly List<RuntimeNode> EntryNodes = new List<RuntimeNode>();
public readonly Dictionary<string, RuntimeNode> 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<RuntimeNode> list, Dictionary<string, RuntimeNode> graphNodes){
RuntimeNodes = graphNodes;
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");
}
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{

@ -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<NodeLink> NodeLinks;
public void ProcessThisNode(){
NodeData.OnProcess();
public class RuntimeNode{
public NodeData NodeData { get; set; }
//the link connect to node's in port
public List<NodeLink> InputLink;
//the link connect to node's out port
public List<NodeLink> 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<string> GetInputNodesId(){
List<string> dependencies = new List<string>();
foreach (NodeLink link in InputLink)
{
dependencies.Add(link.outPort.nodeDataId);
}
return dependencies;
}
}
}

@ -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<string, GetValueDelegate>());
CachedDelegatesForSettingValue.Add(type,new Dictionary<string, SetValueDelegate>());
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<T>(this BlackboardData blackboardData,string path){
var method = RuntimeCache.Instance.CachedDelegatesForGettingValue[blackboardData.GetType()][path];
return (T) method.Invoke(blackboardData);
public static T GetValue<T>(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<T>(this BlackboardData blackboardData,string path,T value){
var method = RuntimeCache.Instance.CachedDelegatesForSettingValue[blackboardData.GetType()][path];
method.Invoke(blackboardData,value);
public static void SetValue<T>(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;
}
}
}

@ -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)){

@ -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);
}
}

@ -376,7 +376,7 @@ namespace TNodeGraphViewImpl.Editor.NodeGraphView{
public override List<Port> 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(){

Loading…
Cancel
Save