You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
466 lines
18 KiB
C#
466 lines
18 KiB
C#
// UltEvents // Copyright 2020 Kybernetik //
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
|
|
namespace UltEvents
|
|
{
|
|
/// <summary>
|
|
/// Allows you to expose the add and remove methods of an <see cref="UltEvent"/> without exposing the rest of its
|
|
/// members such as the ability to invoke it.
|
|
/// </summary>
|
|
public interface IUltEventBase
|
|
{
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Adds the specified 'method to the persistent call list.</summary>
|
|
PersistentCall AddPersistentCall(Delegate method);
|
|
|
|
/// <summary>Removes the specified 'method from the persistent call list.</summary>
|
|
void RemovePersistentCall(Delegate method);
|
|
|
|
/************************************************************************************************************************/
|
|
}
|
|
|
|
/// <summary>
|
|
/// A serializable event which can be viewed and configured in the inspector.
|
|
/// <para></para>
|
|
/// This is a more versatile and user friendly implementation than <see cref="UnityEvent"/>.
|
|
/// </summary>
|
|
[Serializable]
|
|
public abstract class UltEventBase : IUltEventBase
|
|
{
|
|
/************************************************************************************************************************/
|
|
#region Fields and Properties
|
|
/************************************************************************************************************************/
|
|
|
|
public abstract int ParameterCount { get; }
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
[SerializeField]
|
|
internal List<PersistentCall> _PersistentCalls;
|
|
|
|
/// <summary>
|
|
/// The serialized method and parameter details of this event.
|
|
/// </summary>
|
|
public List<PersistentCall> PersistentCallsList
|
|
{
|
|
get { return _PersistentCalls; }
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// The non-serialized method and parameter details of this event.
|
|
/// </summary>
|
|
protected abstract Delegate DynamicCallsBase { get; set; }
|
|
|
|
/// <summary>
|
|
/// Clears the cached invocation list of <see cref="DynamicCallsBase"/>.
|
|
/// </summary>
|
|
[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
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Ensures that `e` isn't null and adds `method` to its <see cref="PersistentCallsList"/>.</summary>
|
|
public static PersistentCall AddPersistentCall<T>(ref T e, Delegate method) where T : UltEventBase, new()
|
|
{
|
|
if (e == null)
|
|
e = new T();
|
|
|
|
return e.AddPersistentCall(method);
|
|
}
|
|
|
|
/// <summary>Ensures that `e` isn't null and adds `method` to its <see cref="PersistentCallsList"/>.</summary>
|
|
public static PersistentCall AddPersistentCall<T>(ref T e, Action method) where T : UltEventBase, new()
|
|
{
|
|
if (e == null)
|
|
e = new T();
|
|
|
|
return e.AddPersistentCall(method);
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>If `e` isn't null, this method removes `method` from its <see cref="PersistentCallsList"/>.</summary>
|
|
public static void RemovePersistentCall(ref UltEventBase e, Delegate method)
|
|
{
|
|
if (e != null)
|
|
e.RemovePersistentCall(method);
|
|
}
|
|
|
|
/// <summary>If `e` isn't null, this method removes `method` from its <see cref="PersistentCallsList"/>.</summary>
|
|
public static void RemovePersistentCall(ref UltEventBase e, Action method)
|
|
{
|
|
if (e != null)
|
|
e.RemovePersistentCall(method);
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Add the specified 'method to the persistent call list.
|
|
/// </summary>
|
|
public PersistentCall AddPersistentCall(Delegate method)
|
|
{
|
|
if (_PersistentCalls == null)
|
|
_PersistentCalls = new List<PersistentCall>(4);
|
|
|
|
var call = new PersistentCall(method);
|
|
_PersistentCalls.Add(call);
|
|
return call;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove the specified 'method from the persistent call list.
|
|
/// </summary>
|
|
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
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/>.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Invokes all <see cref="PersistentCall"/>s registered to this event.</summary>
|
|
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<object> LinkedValueCache = new List<object>();
|
|
private static int _ParameterOffset, _ReturnValueOffset;
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
internal static void UpdateLinkedValueOffsets()
|
|
{
|
|
_ParameterOffset = _ReturnValueOffset = LinkedValueCache.Count;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Stores the `parameter` so it can be accessed by <see cref="PersistentCall"/>s.
|
|
/// </summary>
|
|
protected static void CacheParameter(object parameter)
|
|
{
|
|
LinkedValueCache.Add(parameter);
|
|
_ReturnValueOffset = LinkedValueCache.Count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stores the `parameters` so they can be accessed by <see cref="PersistentCall"/>s.
|
|
/// </summary>
|
|
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
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>The type of each of this event's parameters.</summary>
|
|
public abstract Type[] ParameterTypes { get; }
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
private static readonly Dictionary<Type, ParameterInfo[]>
|
|
EventTypeToParameters = new Dictionary<Type, ParameterInfo[]>();
|
|
|
|
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<Type, string>
|
|
EventTypeToParameterString = new Dictionary<Type, string>();
|
|
|
|
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
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Clears all <see cref="PersistentCallsList"/> and <see cref="DynamicCallsBase"/> registered to this event.
|
|
/// </summary>
|
|
public void Clear()
|
|
{
|
|
if (_PersistentCalls != null)
|
|
_PersistentCalls.Clear();
|
|
|
|
DynamicCallsBase = null;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Returns true if this event has any <see cref="PersistentCallsList"/> or <see cref="DynamicCallsBase"/> registered.
|
|
/// </summary>
|
|
public bool HasCalls
|
|
{
|
|
get
|
|
{
|
|
return (_PersistentCalls != null && _PersistentCalls.Count > 0) || DynamicCallsBase != null;
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Copies the contents of this the `target` event to this event.</summary>
|
|
public virtual void CopyFrom(UltEventBase target)
|
|
{
|
|
if (target._PersistentCalls == null)
|
|
{
|
|
_PersistentCalls = null;
|
|
}
|
|
else
|
|
{
|
|
if (_PersistentCalls == null)
|
|
_PersistentCalls = new List<PersistentCall>();
|
|
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
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Returns a description of this event.</summary>
|
|
public override string ToString()
|
|
{
|
|
var text = new StringBuilder();
|
|
ToString(text);
|
|
return text.ToString();
|
|
}
|
|
|
|
/// <summary>Appends a description of this event.</summary>
|
|
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 ");
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
}
|
|
}
|