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.
CrowdControl/Assets/3rd/Plugins/UltEvents/Events/UltEventBase.cs

466 lines
18 KiB
C#

1 month ago
// 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 ");
}
/************************************************************************************************************************/
}
}