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. 101
      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.
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
* Create graph script a the creator tool
* Node creation based on specified type of graph
* creator tool create graph easily
* Easy port creation via attribute
* Runtime graph
* Blackboard for runtime graph as exposed parameters
* Runtime graph execution
* 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 TTo Convert(TFrom tFrom);
}
public abstract class TwoWayPortTypeConversion<TFrom, TTo> : PortTypeConversion<TFrom,TTo>{
public abstract TFrom ConvertBack(TTo tTo);
}
}

@ -45,11 +45,23 @@ namespace TNodeCore.Runtime.RuntimeCache{
public object Convert(object 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{
public object Convert(object value);
}
public class PropertyNotFoundException : Exception{
@ -126,18 +138,17 @@ namespace TNodeCore.Runtime.RuntimeCache{
else{
}
}
private void CacheRuntimePortTypeConversion(Type type){
if (type.BaseType == null) return;
if (type.BaseType != null){
var genericType = type.BaseType.GetGenericTypeDefinition();
if (genericType != typeof(PortTypeConversion<,>)){
if (genericType != typeof(PortTypeConversion<,>)|| genericType != typeof(TwoWayPortTypeConversion<,>)){
return;
}
}
else{
return;
}
//Forward direction
var type1 = type.BaseType.GetGenericArguments()[0];
var type2 = type.BaseType.GetGenericArguments()[1];
var specificType = typeof(PortConverterHelper<,>).MakeGenericType(type1, type2);
@ -149,17 +160,70 @@ namespace TNodeCore.Runtime.RuntimeCache{
CachedPortConverters.Add(type1,new Dictionary<Type,IPortConverterHelper>());
}
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){
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)){
//Just like above, this function should be checked in here too
if(HasImplicitConversion(from,to)){
CachingImplicitConversion(from,to);
}
return value;
}
return CachedPortConverters[from][to].Convert(value);
}
private bool GetImplcitConvertedValue(Type from, Type to){
throw new NotImplementedException();
}
public List<Type> GetSupportedTypes(Type type){
if(!CachedPortConverters.ContainsKey(type)){
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 ConversionFailedException(string noConverterFoundForType):base(noConverterFoundForType){

@ -23,16 +23,15 @@ namespace TNodeCore.Runtime{
var portType = _portAccessors[portName].Type;
if(portType!=valueType && !portType.IsAssignableFrom(valueType)){
var res =RuntimeCache.RuntimeCache.Instance.GetConvertedValue(valueType, portType, value);
_portAccessors[portName].SetValue(this.NodeData, res);
_portAccessors[portName].SetValue(NodeData, res);
}
else{
_portAccessors[portName].SetValue(this.NodeData,value);
_portAccessors[portName].SetValue(NodeData,value);
}
}
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