feature:Implicit conversion now supported

main
taoria 3 years ago
parent 2d1bd4ba1a
commit 8bdc6afbb3
  1. 33
      README.md
  2. 8
      TNode/Samples.meta
  3. 8
      TNode/Samples/Editor.meta
  4. 17
      TNode/Samples/Editor/HelloEditor.asset
  5. 8
      TNode/Samples/Editor/HelloEditor.asset.meta
  6. 25
      TNode/Samples/Editor/HelloEditor.cs
  7. 14
      TNode/Samples/Editor/HelloEditor.cs.meta
  8. 8
      TNode/Samples/HelloGraph.cs
  9. 11
      TNode/Samples/HelloGraph.cs.meta
  10. 24
      TNode/Samples/New HelloGraph.asset
  11. 8
      TNode/Samples/New HelloGraph.asset.meta
  12. 5
      TNode/TNodeCore/Runtime/Interfaces/IPortTypeConversion.cs
  13. 93
      TNode/TNodeCore/Runtime/RuntimeCache/RuntimeCache.cs
  14. 7
      TNode/TNodeCore/Runtime/RuntimeNode.cs
  15. 8
      TNode/TNodeGtfImpl.meta

@ -3,34 +3,29 @@ Node graph creation tool based on unity experimental graphview and if possible l
the main goal of the repo is to make graph creation easier and more intuitive. the main goal of the repo is to make graph creation easier and more intuitive.
Note **it's not usable and productive on current stage** and need a better
development .
The tool separate its graph editor implementation and the graph creation logic.
# Install
currently under development
# Some Features # Some Features
* Create graph script a the creator tool * creator tool create graph easily
* Node creation based on specified type of graph
* Easy port creation via attribute * Easy port creation via attribute
* Runtime graph * Runtime graph
* Blackboard for runtime graph as exposed parameters * Blackboard for runtime graph as exposed parameters
* Runtime graph execution * Runtime graph execution
* An easy test mode (Runtime graph only) * An easy test mode (Runtime graph only)
* Scene object nodes hold scene objects * Scene object nodes hold scene objects like blackboard
# Some to-dos
* Port connectivity of two types have implicit conversion
* Node placement
* Vertical node
* A universal merger handle multiple input
* Support static graph data traversal
# Install & Usage
Right now this lib is still under development.
# Some To-dos
* Function as port
* Circular dependency support for some situations such as FSM
* Edge colors customization
# Usage
Not yet documented
### Convention

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dc7f08d6196fcc24c97cf3b03543b697
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 15edfa2efedd3854c95e45fefd5caf48
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,17 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 99ad0af56b40495cb6bd6165e652266c, type: 3}
m_Name: HelloEditor
m_EditorClassIdentifier:
graphElementsData: []
graphImplType: 0
autoUpdate: 0

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 060ae44fcac9b534f9bdca3c3e5f1484
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,25 @@
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEngine;
using TNodeCore.Editor;
public class HelloEditor : GraphEditor<HelloGraph>{
[OnOpenAsset]
public static bool OnOpenAsset(int instanceID, int line){
var graph = EditorUtility.InstanceIDToObject(instanceID) as HelloGraph;
if (graph != null) {
var wnd = GetWindow<HelloEditor>();
wnd.titleContent = new GUIContent("EasyGraph Editor");
wnd.Show();
wnd.SetupNonRuntime(graph);
return true;
}
return false;
}
[MenuItem("Window/HelloEditor")]
public static void ShowWindow(){
var res = GetWindow<HelloEditor>();
res.titleContent = new GUIContent("EasyGraph Editor");
res.Show();
}
}

@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 7bffb928791aff340912bba71fc356e9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- m_ViewDataDictionary: {instanceID: 0}
- mVisualTreeAsset: {fileID: 9197481963319205126, guid: b67f6dcbe2361b649ad2b7845207321b, type: 3}
- graphEditorData: {fileID: 11400000, guid: 060ae44fcac9b534f9bdca3c3e5f1484, type: 2}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,8 @@
using UnityEngine;
using System;
using TNodeCore.Runtime.Models;
[CreateAssetMenu(fileName = "New HelloGraph", menuName = "TNode/HelloGraph")]
[Serializable]
public class HelloGraph : GraphData{
}

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2051a0adbd1ba974084a535dd06ab7d7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,24 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 2051a0adbd1ba974084a535dd06ab7d7, type: 3}
m_Name: New HelloGraph
m_EditorClassIdentifier:
nodeList: []
nodeLinks: []
blackboardData:
rid: -2
sceneReference:
references:
version: 2
RefIds:
- rid: -2
type: {class: , ns: , asm: }

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 490933fc590be444780d73cd9f777ed4
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

@ -3,5 +3,10 @@
public abstract class PortTypeConversion<TFrom, TTo>{ public abstract class PortTypeConversion<TFrom, TTo>{
public abstract TTo Convert(TFrom tFrom); public abstract TTo Convert(TFrom tFrom);
} }
public abstract class TwoWayPortTypeConversion<TFrom, TTo> : PortTypeConversion<TFrom,TTo>{
public abstract TFrom ConvertBack(TTo tTo);
}
} }

@ -46,10 +46,22 @@ namespace TNodeCore.Runtime.RuntimeCache{
return _converter.Convert((T1)value); return _converter.Convert((T1)value);
} }
} }
//Store a t1 to t2 conversion but use two way converter's convert back method
internal class PortConverterHelperReverse<T1, T2> : IPortConverterHelper{
private readonly TwoWayPortTypeConversion<T2, T1> _converter;
public object Convert(object value){
return _converter.ConvertBack((T1)value);
}
public PortConverterHelperReverse(Type type){
_converter = Activator.CreateInstance(type) as TwoWayPortTypeConversion<T2, T1>;
}
}
internal interface IPortConverterHelper{ internal interface IPortConverterHelper{
public object Convert(object value); public object Convert(object value);
} }
public class PropertyNotFoundException : Exception{ public class PropertyNotFoundException : Exception{
@ -128,16 +140,15 @@ namespace TNodeCore.Runtime.RuntimeCache{
} }
private void CacheRuntimePortTypeConversion(Type type){ private void CacheRuntimePortTypeConversion(Type type){
if (type.BaseType == null) return;
if (type.BaseType != null){ if (type.BaseType != null){
var genericType = type.BaseType.GetGenericTypeDefinition(); var genericType = type.BaseType.GetGenericTypeDefinition();
if (genericType != typeof(PortTypeConversion<,>)){ if (genericType != typeof(PortTypeConversion<,>)|| genericType != typeof(TwoWayPortTypeConversion<,>)){
return; return;
} }
} }
else{
return;
}
//Forward direction
var type1 = type.BaseType.GetGenericArguments()[0]; var type1 = type.BaseType.GetGenericArguments()[0];
var type2 = type.BaseType.GetGenericArguments()[1]; var type2 = type.BaseType.GetGenericArguments()[1];
var specificType = typeof(PortConverterHelper<,>).MakeGenericType(type1, type2); var specificType = typeof(PortConverterHelper<,>).MakeGenericType(type1, type2);
@ -149,17 +160,70 @@ namespace TNodeCore.Runtime.RuntimeCache{
CachedPortConverters.Add(type1,new Dictionary<Type,IPortConverterHelper>()); CachedPortConverters.Add(type1,new Dictionary<Type,IPortConverterHelper>());
} }
CachedPortConverters[type1].Add(type2,instance); CachedPortConverters[type1].Add(type2,instance);
//Reverse direction
if(type.BaseType.GetGenericTypeDefinition()==typeof(TwoWayPortTypeConversion<,>)){
var specificTypeReverse = typeof(PortConverterHelperReverse<,>).MakeGenericType(type2, type1);
var instanceReverse = Activator.CreateInstance(specificTypeReverse, type) as IPortConverterHelper;
if (instanceReverse == null){
return;
}
if (!CachedPortConverters.ContainsKey(type2)){
CachedPortConverters.Add(type2,new Dictionary<Type,IPortConverterHelper>());
}
CachedPortConverters[type2].Add(type1,instanceReverse);
}
}
private readonly Dictionary<Tuple<Type,Type>,bool> _possibleImplicitConversions = new ();
private bool HasImplicitConversion(Type baseType, Type targetType){
var tuple = new Tuple<Type, Type>(baseType, targetType);
if (_possibleImplicitConversions.ContainsKey(tuple)){
return _possibleImplicitConversions[tuple];
}
var res =baseType.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(mi => mi.Name == "op_Implicit" && mi.ReturnType == targetType)
.Any(mi => {
ParameterInfo pi = mi.GetParameters().FirstOrDefault();
return pi != null && pi.ParameterType == baseType;
});
return _possibleImplicitConversions[tuple] = res;
} }
private void CachingImplicitConversion(Type baseType, Type targetType){
if (HasImplicitConversion(baseType, targetType)) return;
//Create Implicit Conversion Helper that caches the implicit cast function
var typeConverter = Activator.CreateInstance(typeof(ImplicitConversionHelper<,>).MakeGenericType(baseType, targetType)) as IPortConverterHelper;
if (!CachedPortConverters.ContainsKey(baseType)){
CachedPortConverters.Add(baseType,new Dictionary<Type,IPortConverterHelper>());
}
CachedPortConverters[baseType].Add(targetType,typeConverter);
}
public object GetConvertedValue(Type from,Type to,object value){ public object GetConvertedValue(Type from,Type to,object value){
if(!CachedPortConverters.ContainsKey(from)){ if(!CachedPortConverters.ContainsKey(from)){
throw new ConversionFailedException("No converter found for type "+from); //Find the cached port failed ,check if there is an implicit conversion
//This inner cache method would only run once,so add a guard to prevent it run again,even though the function itself has a guard statement.
if(HasImplicitConversion(from,to)){
CachingImplicitConversion(from,to);
}
} }
if(!CachedPortConverters[from].ContainsKey(to)){ if(!CachedPortConverters[from].ContainsKey(to)){
//Just like above, this function should be checked in here too
if(HasImplicitConversion(from,to)){
CachingImplicitConversion(from,to);
}
return value; return value;
} }
return CachedPortConverters[from][to].Convert(value); return CachedPortConverters[from][to].Convert(value);
} }
private bool GetImplcitConvertedValue(Type from, Type to){
throw new NotImplementedException();
}
public List<Type> GetSupportedTypes(Type type){ public List<Type> GetSupportedTypes(Type type){
if(!CachedPortConverters.ContainsKey(type)){ if(!CachedPortConverters.ContainsKey(type)){
return null; return null;
@ -248,6 +312,25 @@ namespace TNodeCore.Runtime.RuntimeCache{
} }
public class ImplicitConversionHelper<T1,T2> : IPortConverterHelper{
public Func<T1, T2> ConvertFunc;
public ImplicitConversionHelper(){
//Caching the implicit method that converts t1 to t2
var method = typeof(T2).GetMethod("op_Implicit", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(T1) }, null);
if (method == null){
//Search it in T1
method = typeof(T1).GetMethod("op_Implicit", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(T2) }, null);
}
//Create the delegate
if (method != null)
ConvertFunc = (Func<T1, T2>) Delegate.CreateDelegate(typeof(Func<T1, T2>), method);
}
public object Convert(object value){
return ConvertFunc((T1) value);
}
}
public class ConversionFailedException : Exception{ public class ConversionFailedException : Exception{
public ConversionFailedException(string noConverterFoundForType):base(noConverterFoundForType){ public ConversionFailedException(string noConverterFoundForType):base(noConverterFoundForType){

@ -23,16 +23,15 @@ namespace TNodeCore.Runtime{
var portType = _portAccessors[portName].Type; var portType = _portAccessors[portName].Type;
if(portType!=valueType && !portType.IsAssignableFrom(valueType)){ if(portType!=valueType && !portType.IsAssignableFrom(valueType)){
var res =RuntimeCache.RuntimeCache.Instance.GetConvertedValue(valueType, portType, value); var res =RuntimeCache.RuntimeCache.Instance.GetConvertedValue(valueType, portType, value);
_portAccessors[portName].SetValue(this.NodeData, res); _portAccessors[portName].SetValue(NodeData, res);
} }
else{ else{
_portAccessors[portName].SetValue(NodeData,value);
_portAccessors[portName].SetValue(this.NodeData,value);
} }
} }
public object GetOutput(string portName){ public object GetOutput(string portName){
return _portAccessors[portName].GetValue(this.NodeData); return _portAccessors[portName].GetValue(NodeData);
} }

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fcfff7d02bd7fbb41a515c4c4359aab2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Loading…
Cancel
Save