using UnityEngine; using System; using System.Collections.Generic; using System.Reflection; using System.Linq; namespace GraphProcessor { /// /// Implement this interface to use the inside your class to define type convertions to use inside the graph. /// Example: /// /// public class CustomConvertions : ITypeAdapter /// { /// public static Vector4 ConvertFloatToVector(float from) => new Vector4(from, from, from, from); /// ... /// } /// /// public abstract class ITypeAdapter // TODO: turn this back into an interface when we have C# 8 { public virtual IEnumerable<(Type, Type)> GetIncompatibleTypes() { yield break; } } public static class TypeAdapter { static Dictionary< (Type from, Type to), Func > adapters = new Dictionary< (Type, Type), Func >(); static Dictionary< (Type from, Type to), MethodInfo > adapterMethods = new Dictionary< (Type, Type), MethodInfo >(); static List< (Type from, Type to)> incompatibleTypes = new List<( Type from, Type to) >(); [System.NonSerialized] static bool adaptersLoaded = false; #if !ENABLE_IL2CPP static Func ConvertTypeMethodHelper(MethodInfo method) { // Convert the slow MethodInfo into a fast, strongly typed, open delegate Func func = (Func)Delegate.CreateDelegate (typeof(Func), method); // Now create a more weakly typed delegate which will call the strongly typed one Func ret = (object param) => func((TParam)param); return ret; } #endif static void LoadAllAdapters() { foreach (Type type in AppDomain.CurrentDomain.GetAllTypes()) { if (typeof(ITypeAdapter).IsAssignableFrom(type)) { if (type.IsAbstract) continue; var adapter = Activator.CreateInstance(type) as ITypeAdapter; if (adapter != null) { foreach (var types in adapter.GetIncompatibleTypes()) { incompatibleTypes.Add((types.Item1, types.Item2)); incompatibleTypes.Add((types.Item2, types.Item1)); } } foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) { if (method.GetParameters().Length != 1) { Debug.LogError($"Ignoring convertion method {method} because it does not have exactly one parameter"); continue; } if (method.ReturnType == typeof(void)) { Debug.LogError($"Ignoring convertion method {method} because it does not returns anything"); continue; } Type from = method.GetParameters()[0].ParameterType; Type to = method.ReturnType; try { #if ENABLE_IL2CPP // IL2CPP doesn't suport calling generic functions via reflection (AOT can't generate templated code) Func r = (object param) => { return (object)method.Invoke(null, new object[]{ param }); }; #else MethodInfo genericHelper = typeof(TypeAdapter).GetMethod("ConvertTypeMethodHelper", BindingFlags.Static | BindingFlags.NonPublic); // Now supply the type arguments MethodInfo constructedHelper = genericHelper.MakeGenericMethod(from, to); object ret = constructedHelper.Invoke(null, new object[] {method}); var r = (Func) ret; #endif adapters.Add((method.GetParameters()[0].ParameterType, method.ReturnType), r); adapterMethods.Add((method.GetParameters()[0].ParameterType, method.ReturnType), method); } catch (Exception e) { Debug.LogError($"Failed to load the type convertion method: {method}\n{e}"); } } } } // Ensure that the dictionary contains all the convertions in both ways // ex: float to vector but no vector to float foreach (var kp in adapters) { if (!adapters.ContainsKey((kp.Key.to, kp.Key.from))) Debug.LogError($"Missing convertion method. There is one for {kp.Key.from} to {kp.Key.to} but not for {kp.Key.to} to {kp.Key.from}"); } adaptersLoaded = true; } public static bool AreIncompatible(Type from, Type to) { if (incompatibleTypes.Any((k) => k.from == from && k.to == to)) return true; return false; } public static bool AreAssignable(Type from, Type to) { if (!adaptersLoaded) LoadAllAdapters(); if (AreIncompatible(from, to)) return false; return adapters.ContainsKey((from, to)); } public static MethodInfo GetConvertionMethod(Type from, Type to) => adapterMethods[(from, to)]; public static object Convert(object from, Type targetType) { if (!adaptersLoaded) LoadAllAdapters(); Func convertionFunction; if (adapters.TryGetValue((from.GetType(), targetType), out convertionFunction)) return convertionFunction?.Invoke(from); return null; } } }