// UltEvents // Copyright 2020 Kybernetik // using System; using System.Reflection; using System.Text; using UnityEngine; using Object = UnityEngine.Object; namespace UltEvents { /// /// Encapsulates a delegate so it can be serialized for . /// [Serializable] public sealed class PersistentCall #if UNITY_EDITOR : ISerializationCallbackReceiver #endif { /************************************************************************************************************************/ #region Fields and Properties /************************************************************************************************************************/ [SerializeField] private Object _Target; /// The object on which the persistent method is called. public Object Target { get { return _Target; } } /************************************************************************************************************************/ [SerializeField] private string _MethodName; /// The name of the persistent method. public string MethodName { get { return _MethodName; } } /************************************************************************************************************************/ [SerializeField] internal PersistentArgument[] _PersistentArguments = NoArguments; /// The arguments which are passed to the method when it is invoked. public PersistentArgument[] PersistentArguments { get { return _PersistentArguments; } } /************************************************************************************************************************/ [NonSerialized] internal MethodBase _Method; /// The method which this call encapsulates. public MethodBase Method { get { if (_Method == null) { Type declaringType; string methodName; GetMethodDetails(out declaringType, out methodName); if (declaringType == null || string.IsNullOrEmpty(methodName)) return null; var argumentCount = _PersistentArguments.Length; var parameters = ArrayCache.GetTempArray(argumentCount); for (int i = 0; i < argumentCount; i++) { parameters[i] = _PersistentArguments[i].SystemType; } if (methodName == "ctor") _Method = declaringType.GetConstructor(UltEventUtils.AnyAccessBindings, null, parameters, null); else _Method = declaringType.GetMethod(methodName, UltEventUtils.AnyAccessBindings, null, parameters, null); } return _Method; } } internal MethodBase GetMethodSafe() { try { return Method; } catch { return null; } } /************************************************************************************************************************/ #if UNITY_EDITOR /************************************************************************************************************************/ // Always clear the cached method in the editor in case the fields have been directly modified by Inspector or Undo operations. void ISerializationCallbackReceiver.OnBeforeSerialize() { ClearCache(); } void ISerializationCallbackReceiver.OnAfterDeserialize() { ClearCache(); } private void ClearCache() { _Method = null; for (int i = 0; i < _PersistentArguments.Length; i++) { _PersistentArguments[i].ClearCache(); } } /************************************************************************************************************************/ #endif /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ /// Constructs a new with default values. public PersistentCall() { } /// Constructs a new to serialize the specified `method`. public PersistentCall(MethodInfo method, Object target) { SetMethod(method, target); } /// Constructs a new to serialize the specified `method`. public PersistentCall(Delegate method) { SetMethod(method); } /// Constructs a new to serialize the specified `method`. public PersistentCall(Action method) { SetMethod(method); } /************************************************************************************************************************/ /// Sets the method which this call encapsulates. public void SetMethod(MethodBase method, Object target) { _Method = method; _Target = target; if (method != null) { if (method.IsStatic || method.IsConstructor) { _MethodName = UltEventUtils.GetFullyQualifiedName(method); _Target = null; } else _MethodName = method.Name; var parameters = method.GetParameters(); if (_PersistentArguments == null || _PersistentArguments.Length != parameters.Length) { _PersistentArguments = NewArgumentArray(parameters.Length); } for (int i = 0; i < _PersistentArguments.Length; i++) { var parameter = parameters[i]; var persistentArgument = _PersistentArguments[i]; persistentArgument.SystemType = parameter.ParameterType; switch (persistentArgument.Type) { case PersistentArgumentType.Parameter: case PersistentArgumentType.ReturnValue: break; default: if ((parameter.Attributes & ParameterAttributes.HasDefault) == ParameterAttributes.HasDefault) { persistentArgument.Value = parameter.DefaultValue; } break; } } } else { _MethodName = null; _PersistentArguments = NoArguments; } } /// Sets the delegate which this call encapsulates. public void SetMethod(Delegate method) { if (method.Target == null) { SetMethod(method.Method, null); } else { var target = method.Target as Object; if (target != null) SetMethod(method.Method, target); else throw new InvalidOperationException("SetMethod failed because action.Target is not a UnityEngine.Object."); } } /// Sets the delegate which this call encapsulates. public void SetMethod(Action method) { SetMethod((Delegate)method); } /************************************************************************************************************************/ private static readonly PersistentArgument[] NoArguments = new PersistentArgument[0]; private static PersistentArgument[] NewArgumentArray(int length) { if (length == 0) { return NoArguments; } else { var array = new PersistentArgument[length]; for (int i = 0; i < length; i++) array[i] = new PersistentArgument(); return array; } } /************************************************************************************************************************/ /// /// Acquire a delegate based on the and and invoke it. /// public object Invoke() { if (Method == null) { Debug.LogWarning("Attempted to Invoke a PersistentCall which couldn't find it's method: " + MethodName); return null; } object[] parameters; if (_PersistentArguments != null && _PersistentArguments.Length > 0) { parameters = ArrayCache.GetTempArray(_PersistentArguments.Length); for (int i = 0; i < parameters.Length; i++) { parameters[i] = _PersistentArguments[i].Value; } } else parameters = null; UltEventBase.UpdateLinkedValueOffsets(); #if UNITY_EDITOR // Somehow Unity ends up getting a UnityEngine.Object which pretends to be null. // But only in the Editor. At runtime it properly deserialized the target as null. // When calling a static method it just gets ignored. But when calling a constructor with a target, it // attempts to apply it to the existing object, which won't work because it's the wrong type. if (_Method.IsConstructor) return _Method.Invoke(null, parameters); #endif return _Method.Invoke(_Target, parameters); } /************************************************************************************************************************/ /// Sets the value of the first persistent argument. public void SetArguments(object argument0) { PersistentArguments[0].Value = argument0; } /// Sets the value of the first and second persistent arguments. public void SetArguments(object argument0, object argument1) { PersistentArguments[0].Value = argument0; PersistentArguments[1].Value = argument1; } /// Sets the value of the first, second, and third persistent arguments. public void SetArguments(object argument0, object argument1, object argument2) { PersistentArguments[0].Value = argument0; PersistentArguments[1].Value = argument1; PersistentArguments[2].Value = argument2; } /// Sets the value of the first, second, third, and fourth persistent arguments. public void SetArguments(object argument0, object argument1, object argument2, object argument3) { PersistentArguments[0].Value = argument0; PersistentArguments[1].Value = argument1; PersistentArguments[2].Value = argument2; PersistentArguments[3].Value = argument3; } /************************************************************************************************************************/ internal void GetMethodDetails(out Type declaringType, out string methodName) { #if UNITY_EDITOR // If you think this looks retarded, that's because it is. // Sometimes Unity ends up with an old reference to an object where the reference thinks it has been // destroyed even though it hasn't and it still has a value Instance ID. So we just get a new reference. if (_Target == null && !ReferenceEquals(_Target, null)) _Target = UnityEditor.EditorUtility.InstanceIDToObject(_Target.GetInstanceID()); #endif GetMethodDetails(_MethodName, _Target, out declaringType, out methodName); } internal static void GetMethodDetails(string serializedMethodName, Object target, out Type declaringType, out string methodName) { if (string.IsNullOrEmpty(serializedMethodName)) { declaringType = null; methodName = null; return; } if (target == null) { var lastDot = serializedMethodName.LastIndexOf('.'); if (lastDot < 0) { declaringType = null; methodName = serializedMethodName; } else { declaringType = Type.GetType(serializedMethodName.Substring(0, lastDot)); lastDot++; methodName = serializedMethodName.Substring(lastDot, serializedMethodName.Length - lastDot); } } else { declaringType = target.GetType(); methodName = serializedMethodName; } } /************************************************************************************************************************/ /// /// Returns true if the specified `type` can be represented by a non-linked . /// public static bool IsSupportedNative(Type type) { return type == typeof(bool) || type == typeof(string) || type == typeof(int) || (type.IsEnum && Enum.GetUnderlyingType(type) == typeof(int)) || type == typeof(float) || type == typeof(Vector2) || type == typeof(Vector3) || type == typeof(Vector4) || type == typeof(Quaternion) || type == typeof(Color) || type == typeof(Color32) || type == typeof(Rect) || type == typeof(Object) || type.IsSubclassOf(typeof(Object)); } /// /// Returns true if the type of each of the `parameters` can be represented by a non-linked . /// public static bool IsSupportedNative(ParameterInfo[] parameters) { for (int i = 0; i < parameters.Length; i++) { if (!IsSupportedNative(parameters[i].ParameterType)) return false; } return true; } /************************************************************************************************************************/ /// Copies the contents of the `target` call to this call. public void CopyFrom(PersistentCall target) { _Target = target._Target; _MethodName = target._MethodName; _Method = target._Method; _PersistentArguments = new PersistentArgument[target._PersistentArguments.Length]; for (int i = 0; i < _PersistentArguments.Length; i++) { _PersistentArguments[i] = target._PersistentArguments[i].Clone(); } } /************************************************************************************************************************/ /// Returns a description of this call. public override string ToString() { var text = new StringBuilder(); ToString(text); return text.ToString(); } /// Appends a description of this call. public void ToString(StringBuilder text) { text.Append("PersistentCall: MethodName="); text.Append(_MethodName); text.Append(", Target="); text.Append(_Target != null ? _Target.ToString() : "null"); text.Append(", PersistentArguments="); UltEventUtils.AppendDeepToString(text, _PersistentArguments.GetEnumerator(), "\n "); } /************************************************************************************************************************/ } }