// UltEvents // Copyright 2020 Kybernetik // using System; using System.Collections.Generic; using System.Reflection; using System.Text; using UnityEngine; using UnityEngine.Events; namespace UltEvents { /// /// Allows you to expose the add and remove methods of an without exposing the rest of its /// members such as the ability to invoke it. /// public interface IUltEventBase { /************************************************************************************************************************/ /// Adds the specified 'method to the persistent call list. PersistentCall AddPersistentCall(Delegate method); /// Removes the specified 'method from the persistent call list. void RemovePersistentCall(Delegate method); /************************************************************************************************************************/ } /// /// A serializable event which can be viewed and configured in the inspector. /// /// This is a more versatile and user friendly implementation than . /// [Serializable] public abstract class UltEventBase : IUltEventBase { /************************************************************************************************************************/ #region Fields and Properties /************************************************************************************************************************/ public abstract int ParameterCount { get; } /************************************************************************************************************************/ [SerializeField] internal List _PersistentCalls; /// /// The serialized method and parameter details of this event. /// public List PersistentCallsList { get { return _PersistentCalls; } } /************************************************************************************************************************/ /// /// The non-serialized method and parameter details of this event. /// protected abstract Delegate DynamicCallsBase { get; set; } /// /// Clears the cached invocation list of . /// [System.Diagnostics.Conditional("UNITY_EDITOR")] protected void OnDynamicCallsChanged() { #if UNITY_EDITOR _DynamicCallInvocationList = null; #endif } /************************************************************************************************************************/ #if UNITY_EDITOR /************************************************************************************************************************/ internal bool HasAnyDynamicCalls() { return DynamicCallsBase != null; } /************************************************************************************************************************/ private Delegate[] _DynamicCallInvocationList; internal Delegate[] GetDynamicCallInvocationList() { if (_DynamicCallInvocationList == null && DynamicCallsBase != null) _DynamicCallInvocationList = DynamicCallsBase.GetInvocationList(); return _DynamicCallInvocationList; } internal int GetDynamicCallInvocationListCount() { if (DynamicCallsBase == null) return 0; else return GetDynamicCallInvocationList().Length; } /************************************************************************************************************************/ #endif /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Operators and Call Registration /************************************************************************************************************************/ /// Ensures that `e` isn't null and adds `method` to its . public static PersistentCall AddPersistentCall(ref T e, Delegate method) where T : UltEventBase, new() { if (e == null) e = new T(); return e.AddPersistentCall(method); } /// Ensures that `e` isn't null and adds `method` to its . public static PersistentCall AddPersistentCall(ref T e, Action method) where T : UltEventBase, new() { if (e == null) e = new T(); return e.AddPersistentCall(method); } /************************************************************************************************************************/ /// If `e` isn't null, this method removes `method` from its . public static void RemovePersistentCall(ref UltEventBase e, Delegate method) { if (e != null) e.RemovePersistentCall(method); } /// If `e` isn't null, this method removes `method` from its . public static void RemovePersistentCall(ref UltEventBase e, Action method) { if (e != null) e.RemovePersistentCall(method); } /************************************************************************************************************************/ /// /// Add the specified 'method to the persistent call list. /// public PersistentCall AddPersistentCall(Delegate method) { if (_PersistentCalls == null) _PersistentCalls = new List(4); var call = new PersistentCall(method); _PersistentCalls.Add(call); return call; } /// /// Remove the specified 'method from the persistent call list. /// public void RemovePersistentCall(Delegate method) { if (_PersistentCalls == null) return; for (int i = 0; i < _PersistentCalls.Count; i++) { var call = _PersistentCalls[i]; if (call.GetMethodSafe() == method.Method && ReferenceEquals(call.Target, method.Target)) { _PersistentCalls.RemoveAt(i); return; } } } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ /// /// Invokes all then all . /// public void DynamicInvoke(params object[] parameters) { // A larger array would actually work fine, but it probably still means something is wrong. if (parameters.Length != ParameterCount) throw new ArgumentException("Invalid parameter count " + parameters.Length + " should be " + ParameterCount); CacheParameters(parameters); InvokePersistentCalls(); var dynamicCalls = DynamicCallsBase; if (dynamicCalls != null) dynamicCalls.DynamicInvoke(parameters); } /************************************************************************************************************************/ /// Invokes all s registered to this event. protected void InvokePersistentCalls() { var originalParameterOffset = _ParameterOffset; var originalReturnValueOffset = _ReturnValueOffset; try { if (_PersistentCalls != null) { for (int i = 0; i < _PersistentCalls.Count; i++) { var result = _PersistentCalls[i].Invoke(); LinkedValueCache.Add(result); _ParameterOffset = originalParameterOffset; _ReturnValueOffset = originalReturnValueOffset; } } } finally { LinkedValueCache.RemoveRange(originalParameterOffset, LinkedValueCache.Count - originalParameterOffset); _ParameterOffset = _ReturnValueOffset = originalParameterOffset; } } /************************************************************************************************************************/ #region Linked Value Cache (Parameters and Returned Values) /************************************************************************************************************************/ private static readonly List LinkedValueCache = new List(); private static int _ParameterOffset, _ReturnValueOffset; /************************************************************************************************************************/ internal static void UpdateLinkedValueOffsets() { _ParameterOffset = _ReturnValueOffset = LinkedValueCache.Count; } /************************************************************************************************************************/ /// /// Stores the `parameter` so it can be accessed by s. /// protected static void CacheParameter(object parameter) { LinkedValueCache.Add(parameter); _ReturnValueOffset = LinkedValueCache.Count; } /// /// Stores the `parameters` so they can be accessed by s. /// protected static void CacheParameters(object[] parameters) { for (int i = 0; i < parameters.Length; i++) LinkedValueCache.Add(parameters[i]); _ReturnValueOffset = LinkedValueCache.Count; } /************************************************************************************************************************/ internal static int ReturnedValueCount { get { return LinkedValueCache.Count - _ReturnValueOffset; } } /************************************************************************************************************************/ internal static object GetParameterValue(int index) { return LinkedValueCache[_ParameterOffset + index]; } internal static object GetReturnedValue(int index) { return LinkedValueCache[_ReturnValueOffset + index]; } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Parameter Display #if UNITY_EDITOR /************************************************************************************************************************/ /// The type of each of this event's parameters. public abstract Type[] ParameterTypes { get; } /************************************************************************************************************************/ private static readonly Dictionary EventTypeToParameters = new Dictionary(); internal ParameterInfo[] Parameters { get { var type = GetType(); ParameterInfo[] parameters; if (!EventTypeToParameters.TryGetValue(type, out parameters)) { var invokeMethod = type.GetMethod("Invoke", ParameterTypes); if (invokeMethod == null || invokeMethod.DeclaringType == typeof(UltEvent) || invokeMethod.DeclaringType.Name.StartsWith(Names.UltEvent.Class + "`")) { parameters = null; } else { parameters = invokeMethod.GetParameters(); } EventTypeToParameters.Add(type, parameters); } return parameters; } } /************************************************************************************************************************/ private static readonly Dictionary EventTypeToParameterString = new Dictionary(); internal string ParameterString { get { var type = GetType(); string parameters; if (!EventTypeToParameterString.TryGetValue(type, out parameters)) { if (ParameterTypes.Length == 0) { parameters = " ()"; } else { var invokeMethodParameters = Parameters; var text = new StringBuilder(); text.Append(" ("); for (int i = 0; i < ParameterTypes.Length; i++) { if (i > 0) text.Append(", "); text.Append(ParameterTypes[i].GetNameCS(false)); if (invokeMethodParameters != null) { text.Append(" "); text.Append(invokeMethodParameters[i].Name); } } text.Append(")"); parameters = text.ToString(); } EventTypeToParameterString.Add(type, parameters); } return parameters; } } /************************************************************************************************************************/ #endif #endregion /************************************************************************************************************************/ /// /// Clears all and registered to this event. /// public void Clear() { if (_PersistentCalls != null) _PersistentCalls.Clear(); DynamicCallsBase = null; } /************************************************************************************************************************/ /// /// Returns true if this event has any or registered. /// public bool HasCalls { get { return (_PersistentCalls != null && _PersistentCalls.Count > 0) || DynamicCallsBase != null; } } /************************************************************************************************************************/ /// Copies the contents of this the `target` event to this event. public virtual void CopyFrom(UltEventBase target) { if (target._PersistentCalls == null) { _PersistentCalls = null; } else { if (_PersistentCalls == null) _PersistentCalls = new List(); else _PersistentCalls.Clear(); for (int i = 0; i < target._PersistentCalls.Count; i++) { var call = new PersistentCall(); call.CopyFrom(target._PersistentCalls[i]); _PersistentCalls.Add(call); } } DynamicCallsBase = target.DynamicCallsBase; #if UNITY_EDITOR _DynamicCallInvocationList = target._DynamicCallInvocationList; #endif } /************************************************************************************************************************/ /// Returns a description of this event. public override string ToString() { var text = new StringBuilder(); ToString(text); return text.ToString(); } /// Appends a description of this event. public void ToString(StringBuilder text) { text.Append(GetType().GetNameCS()); text.Append(": PersistentCalls="); UltEventUtils.AppendDeepToString(text, _PersistentCalls.GetEnumerator(), "\n "); text.Append("\n DynamicCalls="); #if UNITY_EDITOR var invocationList = GetDynamicCallInvocationList(); #else var invocationList = DynamicCallsBase != null ? DynamicCallsBase.GetInvocationList() : null; #endif var enumerator = invocationList != null ? invocationList.GetEnumerator() : null; UltEventUtils.AppendDeepToString(text, enumerator, "\n "); } /************************************************************************************************************************/ } }