Skip to content

Instantly share code, notes, and snippets.

@karlgluck
Last active March 7, 2017 22:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save karlgluck/57560bbe62cd7d7bd3d9eb5770e353cf to your computer and use it in GitHub Desktop.
Save karlgluck/57560bbe62cd7d7bd3d9eb5770e353cf to your computer and use it in GitHub Desktop.
Super simple predeclaration framework for Unity that I'm toying with! Basically you can just declare any delegate field with [Bind] on it and this will look around for a static function or single member on another class that implements it, and hook that up on launch.
using System;
using System.Collections;
using System.Reflection;
using UnityEngine.Scripting;
// [Bind] is used to annotate a static field or property of type Func or Action. It causes
// the value of that member to be mapped to a function somewhere else in the program with the
// same name as that field. The target function can be a static function or an instance
// function whose class has a static property/field of its own type named "Singleton".
//
// Every time you use [Bind] it must be assigned to an appropriate BoundAction or BoundFunc value.
// This is because iOS requires AOT compilation of SingletonMethodCall.Call{Action,Func}{n}{F,P}
// for the appropriate template types.
//
// Examples:
//
// [Bind]
// public static Action<GameObject> AddToScene = BoundAction.Null;
//
// [Bind]
// public static Func<string, GameObject> GetObjectByName = BoundFunc<object>.Null;
//
// [Bind]
// public static Action<int,string,string> SomethingWithLongParameterList = BoundAction.Null;
//
// Note that ".Null" can be used regardless of the parameter set. The language will infer
// the correct types. However, BoundFunc must be used for Func types and BoundAction for
// action types because function binding cannot infer the return type.
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public class Bind : Attribute
{
}
public static class BoundAction
{
public static void Null ()
{
try
{
new Binder.SingletonMethodCall().CallAction0F ();
new Binder.SingletonMethodCall().CallAction0P ();
}
finally
{
throw new NullReferenceException ("Invoked null Action");
}
}
public static void Null<T0> (T0 a0)
{
try
{
new Binder.SingletonMethodCall().CallAction1F (a0);
new Binder.SingletonMethodCall().CallAction1P (a0);
}
finally
{
throw new NullReferenceException ("Invoked null Action");
}
}
public static void Null<T0,T1> (T0 a0, T1 a1)
{
try
{
new Binder.SingletonMethodCall().CallAction2F (a0, a1);
new Binder.SingletonMethodCall().CallAction2P (a0, a1);
}
finally
{
throw new NullReferenceException ("Invoked null Action");
}
}
public static void Null<T0,T1,T2> (T0 a0, T1 a1, T2 a2)
{
try
{
new Binder.SingletonMethodCall().CallAction3F (a0, a1, a2);
new Binder.SingletonMethodCall().CallAction3P (a0, a1, a2);
}
finally
{
throw new NullReferenceException ("Invoked null Action");
}
}
}
public static class BoundFunc<TResult>
{
public static TResult Null<T0> (T0 a0)
{
new Binder.SingletonMethodCall().CallFunc1F<T0,TResult> (a0);
new Binder.SingletonMethodCall().CallFunc1P<T0,TResult> (a0);
return default(TResult);
}
public static TResult Null<T0,T1> (T0 a0, T1 a1)
{
new Binder.SingletonMethodCall().CallFunc2F<T0,T1,TResult> (a0, a1);
new Binder.SingletonMethodCall().CallFunc2P<T0,T1,TResult> (a0, a1);
return default(TResult);
}
public static TResult Null<T0,T1,T2> (T0 a0, T1 a1, T2 a2)
{
new Binder.SingletonMethodCall().CallFunc3F<T0,T1,T2,TResult> (a0, a1, a2);
new Binder.SingletonMethodCall().CallFunc3P<T0,T1,T2,TResult> (a0, a1, a2);
return default(TResult);
}
public static TResult Null<T0,T1,T2,T3> (T0 a0, T1 a1, T2 a2, T3 a3)
{
new Binder.SingletonMethodCall().CallFunc4F<T0,T1,T2,T3,TResult> (a0, a1, a2, a3);
new Binder.SingletonMethodCall().CallFunc4P<T0,T1,T2,T3,TResult> (a0, a1, a2, a3);
return default(TResult);
}
}
// [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
// public class Singleton : Attribute
// {
// public Singleton () : this (0) {}
// public int Priority { get; private set; }
// public Singleton (int priority)
// {
// this.Priority = priority;
// }
// }
// #if UNITY_EDITOR
// [UnityEditor.InitializeOnLoad]
// #endif
public class Binder
{
public static void DoBinding ()
{
foreach (Type type in AllBindableClassTypes())
{
bindInstanceMethods (type);
bindStaticMethods (type);
}
#if UNITY_EDITOR
UnityEngine.Debug.Log ("Binder.cs: -- TODO -- a bunch of this stuff should be optimized");
#endif
}
static IEnumerable AllBindableClassTypes()
{
System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
for (int k = 0; k < assemblies.Length; ++k)
{
Assembly assembly = assemblies[k];
//UnityEngine.Debug.LogFormat ("Assembly[{0}]={1}", k, assembly.FullName);
if (!assembly.FullName.StartsWith("Assembly-CSharp"))
{
continue;
}
Type[] types = assembly.GetTypes();
for (int i = 0; i < types.Length; ++i)
{
Type type = types[i];
if (!type.IsClass)
{
continue;
}
if (type.IsGenericType)
{
// ignore this type
continue;
}
yield return type;
}
}
}
private static bool findSingletonMethod (
FieldInfo delegateFieldToBind,
Type delegateFieldReturnType,
Type[] delegateFieldParameterTypes,
out Type singletonType,
out FieldInfo staticSingletonField,
out PropertyInfo staticSingletonProperty,
out MethodInfo singletonMethod)
{
foreach (Type type in AllBindableClassTypes())
{
FieldInfo thisTypeSingletonField = type.GetField ("Singleton", BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public | BindingFlags.NonPublic);
bool singletonFieldFound = thisTypeSingletonField != null;
if (singletonFieldFound)
{
bool singletonFieldIsWrongType = !thisTypeSingletonField.FieldType.Equals (type);
if (singletonFieldIsWrongType)
{
//Debug.LogFormat ("findSingletonMethod found {0}.Singleton field but it's the wrong type", type.Name);
singletonFieldFound = false;
}
}
PropertyInfo thisTypeSingletonProperty = null;
bool singletonPropertyFound = false;
if (!singletonFieldFound)
{
thisTypeSingletonProperty = type.GetProperty ("Singleton", BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.NonPublic);
singletonPropertyFound = thisTypeSingletonProperty != null;
if (singletonPropertyFound)
{
bool singletonPropertyIsWrongType = !thisTypeSingletonProperty.PropertyType.Equals (type);
if (singletonPropertyIsWrongType)
{
//Debug.LogFormat ("findSingletonMethod found {0}.Singleton property but it's the wrong type", type.Name);
singletonPropertyFound = false;
}
}
}
if (!singletonFieldFound && !singletonPropertyFound)
{
//Debug.LogFormat ("findSingletonMethod didn't find a static {0}.Singleton field or property", type.Name);
continue;
}
//Debug.LogFormat ("findSingletonMethod found {0}.Singleton; checking for {0}.Singleton.{1}...", type.Name, delegateFieldToBind.Name);
MethodInfo thisTypeMethodToBind = type.GetMethod (
delegateFieldToBind.Name,
BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.Public,
null,
delegateFieldParameterTypes,
null);
bool methodNotFound = thisTypeMethodToBind == null;
if (methodNotFound)
{
//Debug.LogFormat ("{0}.Singleton.{1} not found", type.Name, delegateFieldToBind.Name);
continue;
}
bool methodHasWrongReturnType = !thisTypeMethodToBind.ReturnType.Equals(delegateFieldReturnType);
if (methodHasWrongReturnType)
{
//Debug.LogFormat ("{0}.Singleton.{1} has the wrong return type", type.Name, delegateFieldToBind.Name);
continue;
}
//Debug.LogFormat ("{0}.Singleton.{1} found", type.Name, delegateFieldToBind.Name);
singletonType = type;
staticSingletonField = thisTypeSingletonField;
staticSingletonProperty = thisTypeSingletonProperty;
singletonMethod = thisTypeMethodToBind;
return true;
}
singletonType = null;
staticSingletonField = null;
staticSingletonProperty = null;
singletonMethod = null;
return false;
}
private static bool findStaticMethod (
FieldInfo delegateFieldToBind,
Type delegateFieldReturnType,
Type[] delegateFieldParameterTypes,
out MethodInfo staticMethod)
{
foreach (Type type in AllBindableClassTypes())
{
MethodInfo thisTypeMethodToBind = type.GetMethod (
delegateFieldToBind.Name,
BindingFlags.Static | BindingFlags.FlattenHierarchy | BindingFlags.Public,
null,
delegateFieldParameterTypes,
null);
bool methodNotFound = thisTypeMethodToBind == null;
if (methodNotFound)
{
continue;
}
bool methodHasWrongReturnType = !thisTypeMethodToBind.ReturnType.Equals(delegateFieldReturnType);
if (methodHasWrongReturnType)
{
continue;
}
staticMethod = thisTypeMethodToBind;
return true;
}
staticMethod = null;
return false;
}
internal class SingletonMethodCall
{
public FieldInfo Field;
public PropertyInfo Property;
public MethodInfo Method;
public static Delegate Create (FieldInfo singletonField, PropertyInfo singletonProperty, Type delegateType, MethodInfo method)
{
var parameters = method.GetParameters();
bool hasReturnValue = !method.ReturnType.Equals (typeof(void));
var genericMethodName = "Call"+(hasReturnValue?"Func":"Action")+method.GetParameters().Length+(singletonField == null ? "P" : "F");
var genericMethodInfo = typeof(SingletonMethodCall).GetMethod (genericMethodName);
if (genericMethodInfo == null)
{
throw new InvalidOperationException ("Couldn't find method needed to dynamically bind: " + genericMethodName);
}
MethodInfo methodInfo;
if (genericMethodInfo.IsGenericMethod)
{
var genericParameterTypes = new Type[parameters.Length + (hasReturnValue?1:0)];
for (int i = 0; i < parameters.Length; ++i)
{
genericParameterTypes[i] = parameters[i].ParameterType;
}
if (hasReturnValue)
{
genericParameterTypes[parameters.Length] = method.ReturnType;
}
methodInfo = genericMethodInfo.MakeGenericMethod (genericParameterTypes);
}
else
{
methodInfo = genericMethodInfo;
}
var instance = new SingletonMethodCall()
{
Field = singletonField,
Property = singletonProperty,
Method = method,
};
return Delegate.CreateDelegate (delegateType, instance, methodInfo);
}
public void CallAction0F () { Method.Invoke (this.Field.GetValue(null), null); }
public void CallAction1F<T0>(T0 a0) { Method.Invoke (this.Field.GetValue(null), new object[]{a0}); }
public void CallAction2F<T0, T1>(T0 a0, T1 a1) { Method.Invoke (this.Field.GetValue(null), new object[]{a0,a1}); }
public void CallAction3F<T0, T1, T2>(T0 a0, T1 a1, T2 a2) { Method.Invoke (this.Field.GetValue(null), new object[]{a0,a1,a2}); }
public void CallAction4F<T0, T1, T2, T3>(T0 a0, T1 a1, T2 a2, T3 a3) { Method.Invoke (this.Field.GetValue(null), new object[]{a0,a1,a2,a3}); }
public void CallAction5F<T0, T1, T2, T3, T4>(T0 a0, T1 a1, T2 a2, T3 a3, T4 a4) { Method.Invoke (this.Field.GetValue(null), new object[]{a0,a1,a2,a3,a4}); }
public void CallAction6F<T0, T1, T2, T3, T4, T5>(T0 a0, T1 a1, T2 a2, T3 a3, T4 a4, T5 a5) { Method.Invoke (this.Field.GetValue(null), new object[]{a0,a1,a2,a4,a5}); }
public void CallAction0P () { Method.Invoke (this.Property.GetValue(null,null), null); }
public void CallAction1P<T0>(T0 a0) { Method.Invoke (this.Property.GetValue(null,null), new object[]{a0}); }
public void CallAction2P<T0, T1>(T0 a0, T1 a1) { Method.Invoke (this.Property.GetValue(null,null), new object[]{a0,a1}); }
public void CallAction3P<T0, T1, T2>(T0 a0, T1 a1, T2 a2) { Method.Invoke (this.Property.GetValue(null,null), new object[]{a0,a1,a2}); }
public void CallAction4P<T0, T1, T2, T3>(T0 a0, T1 a1, T2 a2, T3 a3) { Method.Invoke (this.Property.GetValue(null,null), new object[]{a0,a1,a2,a3}); }
public void CallAction5P<T0, T1, T2, T3, T4>(T0 a0, T1 a1, T2 a2, T3 a3, T4 a4) { Method.Invoke (this.Property.GetValue(null,null), new object[]{a0,a1,a2,a3,a4}); }
public void CallAction6P<T0, T1, T2, T3, T4, T5>(T0 a0, T1 a1, T2 a2, T3 a3, T4 a4, T5 a5) { Method.Invoke (this.Property.GetValue(null,null), new object[]{a0,a1,a2,a4,a5}); }
public TResult CallFunc0F<TResult>() { return (TResult)Method.Invoke (this.Field.GetValue(null), null); }
public TResult CallFunc1F<T0, TResult>(T0 a0) { return (TResult)Method.Invoke (this.Field.GetValue(null), new object[]{a0}); }
public TResult CallFunc2F<T0, T1, TResult>(T0 a0, T1 a1) { return (TResult)Method.Invoke (this.Field.GetValue(null), new object[]{a0,a1}); }
public TResult CallFunc3F<T0, T1, T2, TResult>(T0 a0, T1 a1, T2 a2) { return (TResult)Method.Invoke (this.Field.GetValue(null), new object[]{a0,a1,a2}); }
public TResult CallFunc4F<T0, T1, T2, T3, TResult>(T0 a0, T1 a1, T2 a2, T3 a3) { return (TResult)Method.Invoke (this.Field.GetValue(null), new object[]{a0,a1,a2,a3}); }
public TResult CallFunc5F<T0, T1, T2, T3, T4, TResult>(T0 a0, T1 a1, T2 a2, T3 a3, T4 a4) { return (TResult)Method.Invoke (this.Field.GetValue(null), new object[]{a0,a1,a2,a3,a4}); }
public TResult CallFunc6F<T0, T1, T2, T3, T4, T5, TResult>(T0 a0, T1 a1, T2 a2, T3 a3, T4 a4, T5 a5) { return (TResult)Method.Invoke (this.Field.GetValue(null), new object[]{a0,a1,a2,a3,a4,a5}); }
public TResult CallFunc0P<TResult>() { return (TResult)Method.Invoke (this.Property.GetValue(null,null), null); }
public TResult CallFunc1P<T0, TResult>(T0 a0) { return (TResult)Method.Invoke (this.Property.GetValue(null,null), new object[]{a0}); }
public TResult CallFunc2P<T0, T1, TResult>(T0 a0, T1 a1) { return (TResult)Method.Invoke (this.Property.GetValue(null,null), new object[]{a0,a1}); }
public TResult CallFunc3P<T0, T1, T2, TResult>(T0 a0, T1 a1, T2 a2) { return (TResult)Method.Invoke (this.Property.GetValue(null,null), new object[]{a0,a1,a2}); }
public TResult CallFunc4P<T0, T1, T2, T3, TResult>(T0 a0, T1 a1, T2 a2, T3 a3) { return (TResult)Method.Invoke (this.Property.GetValue(null,null), new object[]{a0,a1,a2,a3}); }
public TResult CallFunc5P<T0, T1, T2, T3, T4, TResult>(T0 a0, T1 a1, T2 a2, T3 a3, T4 a4) { return (TResult)Method.Invoke (this.Property.GetValue(null,null), new object[]{a0,a1,a2,a3,a4}); }
public TResult CallFunc6P<T0, T1, T2, T3, T4, T5, TResult>(T0 a0, T1 a1, T2 a2, T3 a3, T4 a4, T5 a5) { return (TResult)Method.Invoke (this.Property.GetValue(null,null), new object[]{a0,a1,a2,a3,a4,a5}); }
}
private static void bindStaticMethods (Type type)
{
MemberInfo[] members = type.FindMembers(
MemberTypes.Field,
BindingFlags.Static | BindingFlags.SetField | BindingFlags.Public | BindingFlags.NonPublic,
delegate (MemberInfo memberInfo, object lastParameterOfFindMembers)
{
return memberInfo.MemberType == MemberTypes.Field &&
Attribute.IsDefined (memberInfo, typeof(Bind));
},
null);
for (int j = 0; j < members.Length; ++j)
{
FieldInfo delegateFieldToBind = (FieldInfo)members[j];
Type delegateFieldToBindType = delegateFieldToBind.FieldType;
Type[] parameterTypes = GetDelegateParameterTypes(delegateFieldToBindType);
Type returnType = GetDelegateReturnType(delegateFieldToBindType);
bool alreadyHasValue = delegateFieldToBind.GetValue (null) != null;
if (!alreadyHasValue)
{
#if UNITY_EDITOR
if (returnType != typeof(void))
{
UnityEngine.Debug.LogErrorFormat ("{0}.{1} should be assigned to BoundAction.Null by default (to make sure the AOT compiler generates code for the binding)", type.Name, delegateFieldToBind.Name);
}
else
{
UnityEngine.Debug.LogErrorFormat ("{0}.{1} should be assigned to BoundFunc<{2}>.Null by default (to make sure the AOT compiler generates code for the binding)", type.Name, delegateFieldToBind.Name, returnType.Name);
}
#endif
}
//UnityEngine.Debug.LogFormat ("Attempting to bind {0}.{1}", type.Name, delegateFieldToBind.Name);
Type singletonType;
FieldInfo staticSingletonField;
PropertyInfo staticSingletonProperty;
MethodInfo methodToCall;
bool wasFound = findSingletonMethod (
delegateFieldToBind,
returnType,
parameterTypes,
out singletonType,
out staticSingletonField,
out staticSingletonProperty,
out methodToCall);
if (!wasFound)
{
wasFound = findStaticMethod (
delegateFieldToBind,
returnType,
parameterTypes,
out methodToCall
);
if (!wasFound)
{
throw new InvalidOperationException (string.Format ("Unable to bind {0}.{1}", type.Name, delegateFieldToBind.Name));
}
var staticDelegate = Delegate.CreateDelegate (delegateFieldToBindType, null, methodToCall);
delegateFieldToBind.SetValue (null, staticDelegate);
continue;
}
var dynamicDelegate = SingletonMethodCall.Create (
staticSingletonField,
staticSingletonProperty,
delegateFieldToBindType,
methodToCall);
// UnityEngine.Debug.LogFormat ("Binding {0}", delegateFieldToBind.Name);
delegateFieldToBind.SetValue (null, dynamicDelegate);
}
}
private static void bindInstanceMethods (Type type)
{
MemberInfo[] members = type.FindMembers(
MemberTypes.Field,
BindingFlags.Static | BindingFlags.SetField | BindingFlags.Public | BindingFlags.NonPublic,
delegate (MemberInfo memberInfo, object lastParameterOfFindMembers)
{
return memberInfo.MemberType == MemberTypes.Field &&
Attribute.IsDefined (memberInfo, typeof(Bind));
},
null);
for (int j = 0; j < members.Length; ++j)
{
FieldInfo fieldInfo = (FieldInfo)members[j];
Type fieldType = fieldInfo.FieldType;
Type[] parameterTypes = GetDelegateParameterTypes(fieldType);
bool hasNoParameters = parameterTypes.Length == 0;
if (hasNoParameters)
{
continue;
}
Type[] restOfTheParameterTypes = new Type[parameterTypes.Length-1];
if (restOfTheParameterTypes.Length > 0)
{
Array.Copy (parameterTypes, 1, restOfTheParameterTypes, 0, restOfTheParameterTypes.Length);
}
MethodInfo method = parameterTypes[0].GetMethod (
fieldInfo.Name,
BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.Public,
null,
restOfTheParameterTypes,
null);
bool thisIsNotAnInstanceMethod = method == null;
if (thisIsNotAnInstanceMethod)
{
continue;
}
Delegate delegateFunction = Delegate.CreateDelegate (fieldType, null, method);
fieldInfo.SetValue (null, delegateFunction);
}
}
private static Type[] GetDelegateParameterTypes(Type d)
{
if (d.BaseType != typeof(MulticastDelegate))
throw new ApplicationException("Not a delegate.");
MethodInfo invoke = d.GetMethod("Invoke");
if (invoke == null)
throw new ApplicationException("Not a delegate.");
ParameterInfo[] parameters = invoke.GetParameters();
Type[] typeParameters = new Type[parameters.Length];
for (int i = 0; i < parameters.Length; i++)
{
typeParameters[i] = parameters[i].ParameterType;
}
return typeParameters;
}
private static Type GetDelegateReturnType(Type d)
{
if (d.BaseType != typeof(MulticastDelegate))
throw new ApplicationException("Not a delegate.");
MethodInfo invoke = d.GetMethod("Invoke");
if (invoke == null)
throw new ApplicationException("Not a delegate.");
return invoke.ReturnType;
}
}
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <http://unlicense.org/>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment