diff --git a/README.md b/README.md index 3723cb4..83ce936 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,24 @@ # T-Node -Simple wrapper for unity experimental graphview +Simple wrapper for unity experimental graphview and if possible latter,GTF. +the main goal of the repo is to make graph creation easier and more intuitive. -The main goal is to make graph script easy and clean +Note **it's not usable and productive on current stage** and need a better +development . +and it's mainly for my own use now. + +# Install + +currently under development + +# Features + +1. Create graph script by the creator tool +2. Node creation based on specified type of graph +3. Easy port creation via attribute +4. Runtime graph +5. Blackboard for runtime graph as exposed parameters +6. Runtime graph execution +7. Test Mode (Runtime graph only) + +# Usage -Note it's not fully usable on current stage and need a better development diff --git a/TNodeCore/Editor/NodeGraphView/IBaseDataGraphView.cs b/TNodeCore/Editor/NodeGraphView/IBaseDataGraphView.cs index 14e0ae3..7e04299 100644 --- a/TNodeCore/Editor/NodeGraphView/IBaseDataGraphView.cs +++ b/TNodeCore/Editor/NodeGraphView/IBaseDataGraphView.cs @@ -1,4 +1,5 @@ using TNodeCore.Models; +using TNodeCore.Runtime; using UnityEngine; namespace TNodeCore.Editor.NodeGraphView{ @@ -6,11 +7,20 @@ namespace TNodeCore.Editor.NodeGraphView{ public void AddTNode(NodeData nodeData, Rect rect); public void RemoveTNode(NodeData nodeData); + + public bool TestMode{ get; set; } public void CreateBlackboard(); public GraphData GetGraphData(); public BlackboardData GetBlackboardData(); + + public bool IsRuntimeGraph{ get; set; } + /// + /// Null if it is not a runtime graph + /// + /// + public RuntimeGraph GetRuntimeGraph(); public void SetGraphData(GraphData graph); diff --git a/TNodeCore/Editor/Resources/GraphViewBackground.uss b/TNodeCore/Editor/Resources/GraphViewBackground.uss index e9d3385..6c9fbfc 100644 --- a/TNodeCore/Editor/Resources/GraphViewBackground.uss +++ b/TNodeCore/Editor/Resources/GraphViewBackground.uss @@ -12,4 +12,12 @@ GridBackground{ font-size: 14; +} +#TopMenu{ + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 24px; + background-color: #171717; } \ No newline at end of file diff --git a/TNodeCore/Runtime/RuntimeGraph.cs b/TNodeCore/Runtime/RuntimeGraph.cs index 06cae28..771be99 100644 --- a/TNodeCore/Runtime/RuntimeGraph.cs +++ b/TNodeCore/Runtime/RuntimeGraph.cs @@ -108,16 +108,11 @@ namespace TNodeCore.Runtime{ return null; } //DFS search for resolving dependency - public void StartDependencyTraversal(NodeData startNode,NodeData currentNode,int level=0){ - if (!_build) - Build(); - if(_graphTool==null) - return; + public bool ResolveDependency(NodeData startNode){ + if (_graphTool == null) + return false; _graphTool.DependencyTraversal(Get(startNode)); - var inputNodesId = Get(currentNode).GetInputNodesId(); - foreach (var s in inputNodesId){ - var runtimeNode = Get(s); - } + return true; } private void ModifyOrCreateInNode(NodeLink linkData){ var inNodeId = linkData.inPort.nodeDataId; diff --git a/TNodeCore/Runtime/RuntimeNode.cs b/TNodeCore/Runtime/RuntimeNode.cs index b83e15a..34181fc 100644 --- a/TNodeCore/Runtime/RuntimeNode.cs +++ b/TNodeCore/Runtime/RuntimeNode.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Codice.Client.Common.TreeGrouper; +using TNodeCore.Attribute.Ports; using TNodeCore.Models; using TNodeCore.RuntimeCache; @@ -11,7 +12,8 @@ namespace TNodeCore.Runtime{ public List InputLink; //the link connect to node's out port public List OutputLink; - public Type type; + //Cache node data type for fast access + private readonly Type _type; public void SetInput(string portName,object value){ @@ -20,11 +22,36 @@ namespace TNodeCore.Runtime{ public object GetOutput(string portName){ return NodeData.GetValue(portName); } - + + + + private Dictionary _inputPorts = new(); + private Dictionary _outputPorts = new(); + + + private void CachingPorts(){ + var properties = _type.GetProperties(); + foreach (var propertyInfo in properties){ + //Check if the property is a port + var attribute = propertyInfo.GetCustomAttributes(typeof(InputAttribute),true); + if (attribute.Length > 0){ + + _inputPorts.Add(propertyInfo.Name,NodeData.CacheSetProperty(propertyInfo.Name)); + } + + attribute = propertyInfo.GetCustomAttributes(typeof(OutputAttribute),true); + if (attribute.Length > 0){ + _outputPorts.Add(propertyInfo.Name,NodeData.CacheGetProperty(propertyInfo.Name)); + } + } + } public RuntimeNode(NodeData nodeData){ NodeData = nodeData; //Caching the type of the node - type = nodeData.GetType(); + _type = nodeData.GetType(); + var info = nodeData.GetType().GetProperties(); + + CachingPorts(); } public List GetInputNodesId(){ List dependencies = new List(); diff --git a/TNodeCore/RuntimeCache/RuntimeCache.cs b/TNodeCore/RuntimeCache/RuntimeCache.cs index d3a1a79..dfd88e8 100644 --- a/TNodeCore/RuntimeCache/RuntimeCache.cs +++ b/TNodeCore/RuntimeCache/RuntimeCache.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using PlasticPipe.PlasticProtocol.Messages; using TNodeCore.Attribute; using TNodeCore.Models; +using UnityEngine; namespace TNodeCore.RuntimeCache{ public class RuntimeCache{ @@ -14,12 +16,19 @@ namespace TNodeCore.RuntimeCache{ } //delegate return a value from a nodedata public delegate object GetValueDelegate(IModel nodeData); - public delegate void SetValueDelegate(object nodeData,object value); + public delegate void SetValueDelegate(IModel nodeData,object value); + + public delegate object GetPropertyValueDelegate(); + public delegate void SetPropertyValueDelegate(object value); public readonly Dictionary> CachedDelegatesForGettingValue = new (); public readonly Dictionary> CachedDelegatesForSettingValue = new (); + public readonly Dictionary> CachedDelegatesForGettingPropertyValue = + new (); + public readonly Dictionary> CachedDelegatesForSettingPropertyValue = + new (); private readonly Dictionary _graphBlackboardDictionary = new Dictionary(); @@ -84,16 +93,16 @@ namespace TNodeCore.RuntimeCache{ 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 - - var getValueDelegate = GetValueDelegateForProperty(property); - CachedDelegatesForGettingValue[type].Add(property.Name,getValueDelegate); - - var setValueDelegate = SetValueDelegateForProperty(property); - CachedDelegatesForSettingValue[type].Add(property.Name,setValueDelegate); - } + // var properties = type.GetProperties(); + // foreach(var property in properties){ + // //if the property only has a setter ,skip + // + // var getValueDelegate = GetValueDelegateForProperty(property); + // CachedDelegatesForGettingValue[type].Add(property.Name,getValueDelegate); + // + // var setValueDelegate = SetValueDelegateForProperty(property); + // CachedDelegatesForSettingValue[type].Add(property.Name,setValueDelegate); + // } //register the fields var fields = type.GetFields(); foreach(var field in fields){ @@ -111,19 +120,21 @@ namespace TNodeCore.RuntimeCache{ 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); - } - + // if(property.GetSetMethod(false) != null){ + // var setValueDelegate = SetValueDelegateForProperty(property); + // CachedDelegatesForSettingPropertyValue[type].Add(property.Name,setValueDelegate); + // } + // if(property.GetMethod != null){ + // var getValueDelegate = GetValueDelegateForProperty(property); + // CachedDelegatesForGettingPropertyValue[type].Add(property.Name,getValueDelegate); + // } + } @@ -142,17 +153,21 @@ namespace TNodeCore.RuntimeCache{ } } private GetValueDelegate GetValueDelegateForField(FieldInfo field){ + return field.GetValue; } private SetValueDelegate SetValueDelegateForField(FieldInfo field){ + return field.SetValue; } - private GetValueDelegate GetValueDelegateForProperty(PropertyInfo property){ - var getValueDelegate = (GetValueDelegate)Delegate.CreateDelegate(typeof(GetValueDelegate), property.GetGetMethod()); + private GetPropertyValueDelegate GetValueDelegateForProperty(PropertyInfo property){ + var getValueDelegate = (GetPropertyValueDelegate)Delegate.CreateDelegate(typeof(GetPropertyValueDelegate), property.GetGetMethod()); return getValueDelegate; } - private SetValueDelegate SetValueDelegateForProperty(PropertyInfo property){ - var setValueDelegate = (SetValueDelegate)Delegate.CreateDelegate(typeof(SetValueDelegate), property.GetSetMethod()); + private SetPropertyValueDelegate SetValueDelegateForProperty(PropertyInfo property){ + Debug.Log(property.GetSetMethod()); + + var setValueDelegate = (SetPropertyValueDelegate)Delegate.CreateDelegate(typeof(SetPropertyValueDelegate), property.GetSetMethod()); return setValueDelegate; } @@ -177,12 +192,46 @@ namespace TNodeCore.RuntimeCache{ var method = RuntimeCache.Instance.CachedDelegatesForSettingValue[type??data.GetType()][path]; method.Invoke(data,value); } + public static RuntimeCache.GetValueDelegate GetValueDelegate(this IModel blackboardData,string path){ var method = RuntimeCache.Instance.CachedDelegatesForGettingValue[blackboardData.GetType()][path]; return method; } + /// + /// it generate a delegate that can get the value fast,but it won't cache in runtime cache system,you should put it in somewhere you need + /// + /// + /// + /// + /// + public static RuntimeCache.GetPropertyValueDelegate CacheGetProperty(this IModel data,string path){ + var type = data.GetType(); + var property = type.GetProperty(path); + if (property == null) throw new PropertyNotFoundException(path); + var instance = Delegate.CreateDelegate(typeof(RuntimeCache.GetPropertyValueDelegate), data, + property.GetGetMethod()); + return instance as RuntimeCache.GetPropertyValueDelegate; + } + /// + /// it generate a delegate that can get the value fast,but it won't cache in runtime cache system,you should put it in somewhere you need + /// + /// + /// + /// + /// + public static RuntimeCache.SetPropertyValueDelegate CacheSetProperty(this IModel data,string path){ + var type = data.GetType(); + var property = type.GetProperty(path); + if (property == null) throw new PropertyNotFoundException(path); + var instance = Delegate.CreateDelegate(typeof(RuntimeCache.SetPropertyValueDelegate), data, + property.GetSetMethod()); + return instance as RuntimeCache.SetPropertyValueDelegate; + } + } - - + public class PropertyNotFoundException : Exception{ + public PropertyNotFoundException(string path):base("Property not found :"+path){ + + } } } \ No newline at end of file diff --git a/TNodeGraphViewImpl/Editor/Inspector/NodeInspectorInNode.cs b/TNodeGraphViewImpl/Editor/Inspector/NodeInspectorInNode.cs index 8d44a8c..702151d 100644 --- a/TNodeGraphViewImpl/Editor/Inspector/NodeInspectorInNode.cs +++ b/TNodeGraphViewImpl/Editor/Inspector/NodeInspectorInNode.cs @@ -1,7 +1,9 @@ using System.Reflection; using TNodeCore.Attribute; +using TNodeCore.Editor.NodeGraphView; using TNodeCore.Editor.Serialization; using TNodeCore.Models; +using TNodeGraphViewImpl.Editor.NodeViews; using UnityEditor; using UnityEditor.UIElements; using UnityEngine; @@ -36,9 +38,7 @@ 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))){ @@ -57,16 +57,33 @@ namespace TNode.Editor.Inspector{ } - if (_data.isTest){ + var globalTest = GetFirstAncestorOfType()?.BaseDataGraphView?.TestMode; + if(globalTest??false){ + CreateTestButton(); + } + else 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); + CreateTestButton(); } } - + + private void CreateTestButton(){ + var testButton = new Button(() => { + var test = GetFirstAncestorOfType(); + if (test != null){ + if(!test.IsRuntimeGraph) return; + var runtimeGraph = test.GetRuntimeGraph(); + if (runtimeGraph != null){ + runtimeGraph.ResolveDependency(_data); + } + _data.OnTest(); + } + }){ + text = "Test" + }; + + Add(testButton); + + } } } \ No newline at end of file diff --git a/TNodeGraphViewImpl/Editor/NodeGraphView/DataGraphView.cs b/TNodeGraphViewImpl/Editor/NodeGraphView/DataGraphView.cs index e23b499..f07e68c 100644 --- a/TNodeGraphViewImpl/Editor/NodeGraphView/DataGraphView.cs +++ b/TNodeGraphViewImpl/Editor/NodeGraphView/DataGraphView.cs @@ -12,13 +12,13 @@ using TNodeCore.Runtime; using TNodeGraphViewImpl.Editor.Cache; using TNodeGraphViewImpl.Editor.GraphBlackboard; using TNodeGraphViewImpl.Editor.NodeViews; - using UnityEditor; using UnityEditor.Experimental.GraphView; -using UnityEditor.VersionControl; using UnityEngine; using UnityEngine.UIElements; + + using Edge = UnityEditor.Experimental.GraphView.Edge; namespace TNodeGraphViewImpl.Editor.NodeGraphView{ @@ -65,7 +65,7 @@ namespace TNodeGraphViewImpl.Editor.NodeGraphView{ SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale); RegisterDragEvent(); OnInit(); - CheckAfterInit(); + } /// /// Probably reusable in later GTFs version @@ -144,7 +144,7 @@ namespace TNodeGraphViewImpl.Editor.NodeGraphView{ } }; } - private void CheckAfterInit(){ + private void CheckDataAfterInit(){ if(Data == null){ WaitingForAGraph(); } @@ -173,7 +173,39 @@ namespace TNodeGraphViewImpl.Editor.NodeGraphView{ private void OnInit(){ ConstructDefaultBehaviour(); OnGraphViewCreate(); + CheckDataAfterInit(); } + + protected void CreateMenu(){ + var visualElement = new VisualElement{ + name = "TopMenu" + }; + visualElement.style.position = Position.Absolute; + visualElement.style.top = 0; + visualElement.style.backgroundColor = new StyleColor(new Color(0.1f, 0.1f, 0.1f, 1)); + Add(visualElement); + visualElement.style.flexDirection = FlexDirection.Row; + + + //Add a toggle button to toggle test mode + var testModeToggle = new Toggle{ + name = "TestModeToggle", + label = "Test Mode", + value = false + }; + testModeToggle.RegisterValueChangedCallback(evt => { + if (evt.newValue){ + TestMode = true; + } + else{ + TestMode = false; + } + }); + visualElement.Add(testModeToggle); + + + } + public void RegisterDragEvent(){ RegisterCallback(OnDragUpdated); RegisterCallback(OnDragPerform); @@ -437,6 +469,8 @@ namespace TNodeGraphViewImpl.Editor.NodeGraphView{ Owner.graphEditorData.graphElementsData.RemoveAll(x => x.guid == nodeData.id); } + public bool TestMode{ get; set; } + public void CreateBlackboard(){ _blackboard = NodeEditorExtensions.CreateBlackboardWithGraphData(typeof(T)); _blackboard.Setup(this,Owner); @@ -463,6 +497,10 @@ namespace TNodeGraphViewImpl.Editor.NodeGraphView{ public bool IsRuntimeGraph{ get; set; } + public RuntimeGraph GetRuntimeGraph(){ + return _runtimeGraph; + } + public void SetGraphData(GraphData graph){ Data = graph as T; } diff --git a/TNodeGraphViewImpl/Editor/NodeViews/NodeView.cs b/TNodeGraphViewImpl/Editor/NodeViews/NodeView.cs index fe6a69e..5a9c81e 100644 --- a/TNodeGraphViewImpl/Editor/NodeViews/NodeView.cs +++ b/TNodeGraphViewImpl/Editor/NodeViews/NodeView.cs @@ -4,6 +4,7 @@ using System.Reflection; using TNode.Editor.Inspector; using TNodeCore; using TNodeCore.Attribute.Ports; +using TNodeCore.Editor.NodeGraphView; using TNodeCore.Editor.Serialization; using TNodeCore.Models; using UnityEditor.Experimental.GraphView; @@ -15,7 +16,13 @@ namespace TNodeGraphViewImpl.Editor.NodeViews{ public abstract class BaseNodeView : Node,INodeView where T:NodeData,new(){ protected T _data; private readonly NodeInspectorInNode _nodeInspectorInNode; - + + public IBaseDataGraphView BaseDataGraphView{ + get{ + var visualElement = this.GetFirstAncestorOfType() as IBaseDataGraphView; + return visualElement; + } + } public T Data{ get => _data; set{ @@ -180,6 +187,7 @@ namespace TNodeGraphViewImpl.Editor.NodeViews{ public NodeData GetNodeData(); public void OnDataModified(); + IBaseDataGraphView BaseDataGraphView{ get; } } public interface INodeView:IBaseNodeView where T:NodeData,new(){