initial commit

This commit is contained in:
Jo 2025-01-07 02:06:59 +01:00
parent 6715289efe
commit 788c3389af
37645 changed files with 2526849 additions and 80 deletions

View file

@ -0,0 +1,46 @@
// UltEvents // Copyright 2021 Kybernetik //
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("UltEvents")]
[assembly: AssemblyDescription("An serializable event system for Unity with superior features to the inbuilt UnityEvents.")]
[assembly: AssemblyCompany("Kybernetik")]
[assembly: AssemblyProduct("UltEvents")]
[assembly: AssemblyCopyright("Copyright © Kybernetik 2021")]
[assembly: ComVisible(false)]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: SuppressMessage("Style", "IDE0016:Use 'throw' expression",
Justification = "Not supported by older Unity versions.")]
[assembly: SuppressMessage("Style", "IDE0018:Inline variable declaration",
Justification = "Not supported by older Unity versions.")]
[assembly: SuppressMessage("Style", "IDE0019:Use pattern matching",
Justification = "Not supported by older Unity versions.")]
[assembly: SuppressMessage("Style", "IDE0031:Use null propagation",
Justification = "Not supported by older Unity versions.")]
[assembly: SuppressMessage("Style", "IDE0041:Use 'is null' check",
Justification = "Not supported by older Unity versions.")]
[assembly: SuppressMessage("Style", "IDE0044:Make field readonly",
Justification = "Using the [SerializeField] attribute on a private field means Unity will set it from serialized data.")]
[assembly: SuppressMessage("Code Quality", "IDE0051:Remove unused private members",
Justification = "Unity messages can be private, but the IDE will not know that Unity can still call them.")]
[assembly: SuppressMessage("Code Quality", "IDE0052:Remove unread private members",
Justification = "Unity messages can be private and don't need to be called manually.")]
[assembly: SuppressMessage("Style", "IDE0059:Value assigned to symbol is never used",
Justification = "Inline variable declarations are not supported by older Unity versions.")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter",
Justification = "Unity messages sometimes need specific signatures, even if you don't use all the parameters.")]
[assembly: SuppressMessage("Style", "IDE0063:Use simple 'using' statement",
Justification = "Not supported by older Unity versions.")]
[assembly: SuppressMessage("Style", "IDE0066:Convert switch statement to expression",
Justification = "Not supported by older Unity versions.")]
[assembly: SuppressMessage("Style", "IDE1005:Delegate invocation can be simplified.",
Justification = "Not supported by older Unity versions.")]
[assembly: SuppressMessage("Style", "IDE1006:Naming Styles",
Justification = "Don't give code style advice in publically released code.")]
// This suppression doesn't seem to actually work so we need to put #pragma warning disable in every file :(
//[assembly: SuppressMessage("Code Quality", "CS0649:Field is never assigned to, and will always have its default value",
// Justification = "Using the [SerializeField] attribute on a private field means Unity will set it from serialized data.")]

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 238d757ae4fd9d748adb89c3e5ce1590
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bde51928ec736738738f4f0970b09edf
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,50 @@
// UltEvents // Copyright 2021 Kybernetik //
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
namespace UltEvents.Benchmarks
{
/// <summary>[Editor-Only]
/// A simple performance test that loads and instantiates a prefab to test how long it takes.
/// </summary>
[AddComponentMenu("")]// Don't show in the Add Component menu. You need to drag this script onto a prefab manually.
[HelpURL(UltEventUtils.APIDocumentationURL + "/Behchmarks/EventBenchmark")]
public sealed class EventBenchmark : MonoBehaviour
{
/************************************************************************************************************************/
[SerializeField]
private string _PrefabPath;
/************************************************************************************************************************/
private void Update()
{
// Wait a bit to avoid mixing this performance test in with the engine startup processes.
if (Time.timeSinceLevelLoad < 1)
return;
// Sleep to make this frame show up easily in the Unity Profiler.
System.Threading.Thread.Sleep(100);
var start = EditorApplication.timeSinceStartup;
// Include the costs of loading and instantiating the prefab as well as the actual event invocation.
var prefab = Resources.Load<GameObject>(_PrefabPath);
Instantiate(prefab);
Debug.Log(EditorApplication.timeSinceStartup - start);
enabled = false;
}
/************************************************************************************************************************/
}
}
#endif

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 497712a3d29c46645b6633712f5975da
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,38 @@
// UltEvents // Copyright 2021 Kybernetik //
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
namespace UltEvents.Benchmarks
{
/// <summary>[Editor-Only]
/// A simple performance test which calls <see cref="Test"/> in <see cref="Awake"/> and logs the amount of time it
/// takes.
/// </summary>
[AddComponentMenu("")]// Don't show in the Add Component menu. You need to drag this script onto a prefab manually.
[HelpURL(UltEventUtils.APIDocumentationURL + "/Behchmarks/EventBenchmarkBase")]
public abstract class EventBenchmarkBase : MonoBehaviour
{
/************************************************************************************************************************/
private void Awake()
{
var start = EditorApplication.timeSinceStartup;
Test();
Debug.Log(EditorApplication.timeSinceStartup - start);
}
/************************************************************************************************************************/
/// <summary>Called by <see cref="Awake"/>.</summary>
protected abstract void Test();
/************************************************************************************************************************/
}
}
#endif

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2ecd8d6ecfcb0d1409a227c2b117a74e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,35 @@
// UltEvents // Copyright 2021 Kybernetik //
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
#if UNITY_EDITOR
using UnityEngine;
namespace UltEvents.Benchmarks
{
/// <summary>[Editor-Only]
/// A simple performance test which invokes a <see cref="UltEvent"/>.
/// </summary>
[AddComponentMenu("")]// Don't show in the Add Component menu. You need to drag this script onto a prefab manually.
[HelpURL(UltEventUtils.APIDocumentationURL + "/Behchmarks/UltEventBenchmark")]
public sealed class UltEventBenchmark : EventBenchmarkBase
{
/************************************************************************************************************************/
[SerializeField]
private UltEvent _Event;
/************************************************************************************************************************/
/// <summary>Called by <see cref="Awake"/>.</summary>
protected override void Test()
{
_Event.Invoke();
}
/************************************************************************************************************************/
}
}
#endif

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: da9e6f3d7b0a35d48bbb43b5b52529e9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,36 @@
// UltEvents // Copyright 2021 Kybernetik //
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
#if UNITY_EDITOR
using UnityEngine;
using UnityEngine.Events;
namespace UltEvents.Benchmarks
{
/// <summary>[Editor-Only]
/// A simple performance test which invokes a <see cref="UnityEvent"/>.
/// </summary>
[AddComponentMenu("")]// Don't show in the Add Component menu. You need to drag this script onto a prefab manually.
[HelpURL(UltEventUtils.APIDocumentationURL + "/Behchmarks/UnityEventBenchmark")]
public sealed class UnityEventBenchmark : EventBenchmarkBase
{
/************************************************************************************************************************/
[SerializeField]
private UnityEvent _Event;
/************************************************************************************************************************/
/// <summary>Called by <see cref="Awake"/>.</summary>
protected override void Test()
{
_Event.Invoke();
}
/************************************************************************************************************************/
}
}
#endif

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 84601744a18dc8d48a9e3068c3ec5aa5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dfdd566daeabed6e1fd9a3abe2f43817
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,179 @@
// UltEvents // Copyright 2021 Kybernetik //
#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEngine;
namespace UltEvents.Editor
{
/// <summary>[Editor-Only]
/// A simple wrapper around <see cref="EditorPrefs"/> to get and set a bool.
/// <para></para>
/// If you are interested in a more comprehensive pref wrapper that supports more types, you should check out
/// <see href="https://kybernetik.com.au/inspector-gadgets">Inspector Gadgets</see>.
/// </summary>
public sealed class BoolPref
{
/************************************************************************************************************************/
/// <summary>The text that will be displayed for this item in a context menu.</summary>
public readonly string Label;
/// <summary>The identifier with which this pref will be saved.</summary>
public readonly string Key;
/// <summary>The starting value to use for this pref if none was previously saved.</summary>
public readonly bool DefaultValue;
/// <summary>Called when the value is changed.</summary>
public readonly Action OnChanged;
/************************************************************************************************************************/
private bool _HasValue;
private bool _Value;
/// <summary>The current value of this pref.</summary>
public bool Value
{
get
{
if (!_HasValue)
{
_HasValue = true;
_Value = EditorPrefs.GetBool(Key, DefaultValue);
}
return _Value;
}
set
{
_Value = value;
EditorPrefs.SetBool(Key, value);
_HasValue = true;
}
}
/// <summary>Returns the current value of the `pref`.</summary>
public static implicit operator bool(BoolPref pref)
{
return pref.Value;
}
/************************************************************************************************************************/
/// <summary>Constructs a new <see cref="BoolPref"/>.</summary>
public BoolPref(string label, bool defaultValue, Action onChanged = null)
{
Label = label;
Key = Names.Namespace + "." + label;
DefaultValue = defaultValue;
OnChanged = onChanged;
_Value = EditorPrefs.GetBool(Key, defaultValue);
}
/************************************************************************************************************************/
/// <summary>Adds a menu item to toggle this pref.</summary>
public void AddToMenu(GenericMenu menu)
{
menu.AddItem(new GUIContent("Display Options ->/" + Label), _Value, () =>
{
_Value = !_Value;
EditorPrefs.SetBool(Key, _Value);
if (OnChanged != null)
OnChanged();
});
}
/************************************************************************************************************************/
/// <summary>Various settings.</summary>
public static readonly BoolPref
UseIndentation = new BoolPref("Use Indentation", true);
/************************************************************************************************************************/
/// <summary>Various settings.</summary>
public static readonly BoolPref
AutoOpenMenu = new BoolPref("Auto Open Menu", true);
/************************************************************************************************************************/
/// <summary>Various settings.</summary>
public static readonly BoolPref
AutoHideFooter = new BoolPref("Auto Hide Footer", true);
/************************************************************************************************************************/
/// <summary>Various settings.</summary>
public static readonly BoolPref
ShowNonPublicMethods = new BoolPref("Show Non-Public Methods", true);
/************************************************************************************************************************/
/// <summary>Various settings.</summary>
public static readonly BoolPref
GroupNonPublicMethods = new BoolPref("Group Non-Public Methods", true);
/************************************************************************************************************************/
/// <summary>Various settings.</summary>
public static readonly BoolPref
ShowStaticMethods = new BoolPref("Show Static Methods", true);
/************************************************************************************************************************/
/// <summary>Various settings.</summary>
public static readonly BoolPref
ShowFullTypeNames = new BoolPref("Use Full Type Names", false);
/************************************************************************************************************************/
/// <summary>Various settings.</summary>
public static readonly BoolPref
GroupMethodOverloads = new BoolPref("Sub-Menu for Method Overloads", true);
/************************************************************************************************************************/
/// <summary>Various settings.</summary>
public static readonly BoolPref
SubMenuForEachBaseType = new BoolPref("Base Types ->/Individual Sub-Menus", true, MethodSelectionMenu.ClearMemberCache);
/************************************************************************************************************************/
/// <summary>Various settings.</summary>
public static readonly BoolPref
SubMenuForBaseTypes = new BoolPref("Base Types ->/Group Sub-Menu", true);
/************************************************************************************************************************/
/// <summary>Various settings.</summary>
public static readonly BoolPref
SubMenuForRootBaseType = new BoolPref("Base Types ->/Group Root Type", false);
/************************************************************************************************************************/
/// <summary>Adds menu items to toggle all prefs.</summary>
public static void AddDisplayOptions(GenericMenu menu)
{
UseIndentation.AddToMenu(menu);
AutoOpenMenu.AddToMenu(menu);
AutoHideFooter.AddToMenu(menu);
ShowNonPublicMethods.AddToMenu(menu);
GroupNonPublicMethods.AddToMenu(menu);
ShowStaticMethods.AddToMenu(menu);
ShowFullTypeNames.AddToMenu(menu);
GroupMethodOverloads.AddToMenu(menu);
SubMenuForEachBaseType.AddToMenu(menu);
SubMenuForBaseTypes.AddToMenu(menu);
SubMenuForRootBaseType.AddToMenu(menu);
}
/************************************************************************************************************************/
}
}
#endif

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 87441e384370a30428ebd73357e740d6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,124 @@
// UltEvents // Copyright 2021 Kybernetik //
#if UNITY_EDITOR
using System;
using UnityEditor;
using Object = UnityEngine.Object;
namespace UltEvents.Editor
{
/// <summary>[Editor-Only]
/// Manages the copying and pasting of events and persistent calls.
/// </summary>
public static class Clipboard
{
/************************************************************************************************************************/
private static UltEventBase _Event;
/// <summary>Indicates whether an event has been copied.</summary>
public static bool HasEvent { get { return _Event != null; } }
/************************************************************************************************************************/
/// <summary>Stores the details of the specified event.</summary>
public static void CopyEvent(UltEventBase e)
{
var eventType = e.GetType();
if (_Event == null || _Event.GetType() != eventType)
_Event = (UltEventBase)Activator.CreateInstance(eventType);
_Event.CopyFrom(e);
}
/// <summary>Stores the details of the event contained in the specified property.</summary>
public static void CopyEvent(Serialization.PropertyAccessor accessor, Object target)
{
var e = (UltEventBase)accessor.GetValue(target);
CopyEvent(e);
}
/// <summary>Stores the details of the event contained in the specified property.</summary>
public static void CopyEvent(SerializedProperty property)
{
var accessor = property.GetAccessor();
if (accessor == null)
return;
CopyEvent(accessor, property.serializedObject.targetObject);
}
/************************************************************************************************************************/
/// <summary>Overwrites the specified event with the previously copied details.</summary>
public static void Paste(UltEventBase e)
{
e.CopyFrom(_Event);
}
/************************************************************************************************************************/
private static PersistentCall _Call;
/// <summary>Indicates whether a persistent call has been copied.</summary>
public static bool HasCall { get { return _Call != null; } }
/************************************************************************************************************************/
/// <summary>Stores the details of the specified call.</summary>
public static void CopyCall(PersistentCall call)
{
if (_Call == null)
_Call = new PersistentCall();
_Call.CopyFrom(call);
}
/// <summary>Stores the details of the call contained in the specified property.</summary>
public static void CopyCall(Serialization.PropertyAccessor accessor, Object target)
{
var call = (PersistentCall)accessor.GetValue(target);
CopyCall(call);
}
/// <summary>Stores the details of the call contained in the specified property.</summary>
public static void CopyCall(SerializedProperty property)
{
var accessor = property.GetAccessor();
if (accessor == null)
return;
CopyCall(accessor, property.serializedObject.targetObject);
}
/************************************************************************************************************************/
/// <summary>Overwrites the specified call with the previously copied details.</summary>
public static void PasteCall(PersistentCall call)
{
call.CopyFrom(_Call);
}
/// <summary>Overwrites the call contained in the specified property with the copied details.</summary>
public static void PasteCall(Serialization.PropertyAccessor accessor, Object target)
{
var call = (PersistentCall)accessor.GetValue(target);
PasteCall(call);
}
/// <summary>Overwrites the call contained in the specified property with the copied details.</summary>
public static void PasteCall(SerializedProperty property)
{
property.ModifyValues<PersistentCall>((call) =>
{
PasteCall(call);
}, "Paste PersistentCall");
}
/************************************************************************************************************************/
}
}
#endif

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 430d5eba154fa6c4a8b4511c67734546
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,375 @@
// UltEvents // Copyright 2021 Kybernetik //
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
namespace UltEvents.Editor
{
/// <summary>[Editor-Only]
/// Manages the GUI state used when drawing events.
/// </summary>
internal sealed class DrawerState
{
/************************************************************************************************************************/
/// <summary>The currently active state.</summary>
public static readonly DrawerState Current = new DrawerState();
/************************************************************************************************************************/
/// <summary>The <see cref="SerializedProperty"/> for the event currently being drawn.</summary>
public SerializedProperty EventProperty { get; private set; }
/// <summary>The event currently being drawn.</summary>
public UltEventBase Event { get; private set; }
/************************************************************************************************************************/
/// <summary>The <see cref="SerializedProperty"/> for the call currently being drawn.</summary>
public SerializedProperty CallProperty { get; private set; }
/// <summary>The <see cref="SerializedProperty"/> for the target of the call currently being drawn.</summary>
public SerializedProperty TargetProperty { get; private set; }
/// <summary>The <see cref="SerializedProperty"/> for the method name of the call currently being drawn.</summary>
public SerializedProperty MethodNameProperty { get; private set; }
/// <summary>The <see cref="SerializedProperty"/> for the persistent arguments array of the call currently being drawn.</summary>
public SerializedProperty PersistentArgumentsProperty { get; private set; }
/// <summary>The index of the call currently being drawn.</summary>
public int callIndex = -1;
/// <summary>The call currently being drawn.</summary>
public PersistentCall call;
/// <summary>The parameters of the call currently being drawn.</summary>
public ParameterInfo[] callParameters;
/// <summary>The index of the parameter currently being drawn.</summary>
public int parameterIndex;
/************************************************************************************************************************/
/// <summary>If true, each call will be stored so that subsequent calls can link to their return value.</summary>
public bool CachePreviousCalls { get; private set; }
/// <summary>The calls of the current event that come before the current call currently being drawn.</summary>
private readonly List<PersistentCall> PreviousCalls = new List<PersistentCall>();
/// <summary>The methods targeted by the calls of the event currently being drawn.</summary>
private readonly List<MethodBase> PersistentMethodCache = new List<MethodBase>();
/************************************************************************************************************************/
/// <summary>The parameter currently being drawn.</summary>
public ParameterInfo CurrentParameter
{
get { return callParameters[parameterIndex]; }
}
/************************************************************************************************************************/
/// <summary>Caches the event from the specified property and returns true as long as it is not null.</summary>
public bool TryBeginEvent(SerializedProperty eventProperty)
{
Event = eventProperty.GetValue<UltEventBase>();
if (Event == null)
return false;
EventProperty = eventProperty;
return true;
}
/// <summary>Cancels out a call to <see cref="TryBeginEvent"/>.</summary>
public void EndEvent()
{
EventProperty = null;
Event = null;
}
/************************************************************************************************************************/
/// <summary>Starts caching calls so that subsequent calls can link to earlier return values.</summary>
public void BeginCache()
{
CacheLinkedArguments();
CachePreviousCalls = true;
}
/// <summary>Cancels out a call to <see cref="EndCache"/>.</summary>
public void EndCache()
{
CachePreviousCalls = false;
PreviousCalls.Clear();
}
/************************************************************************************************************************/
/// <summary>Caches the call from the specified property.</summary>
public void BeginCall(SerializedProperty callProperty)
{
CallProperty = callProperty;
TargetProperty = GetTargetProperty(callProperty);
MethodNameProperty = GetMethodNameProperty(callProperty);
PersistentArgumentsProperty = GetPersistentArgumentsProperty(callProperty);
call = GetCall(callProperty);
}
/// <summary>Cancels out a call to <see cref="BeginCall"/>.</summary>
public void EndCall()
{
if (CachePreviousCalls)
PreviousCalls.Add(call);
call = null;
}
/************************************************************************************************************************/
/// <summary>Returns the property encapsulating the <see cref="PersistentCall.Target"/>.</summary>
public static SerializedProperty GetTargetProperty(SerializedProperty callProperty)
{
return callProperty.FindPropertyRelative(Names.PersistentCall.Target);
}
/// <summary>Returns the property encapsulating the <see cref="PersistentCall.MethodName"/>.</summary>
public static SerializedProperty GetMethodNameProperty(SerializedProperty callProperty)
{
return callProperty.FindPropertyRelative(Names.PersistentCall.MethodName);
}
/// <summary>Returns the property encapsulating the <see cref="PersistentCall.PersistentArguments"/>.</summary>
public static SerializedProperty GetPersistentArgumentsProperty(SerializedProperty callProperty)
{
return callProperty.FindPropertyRelative(Names.PersistentCall.PersistentArguments);
}
/// <summary>Returns the call encapsulated by the specified property.</summary>
public static PersistentCall GetCall(SerializedProperty callProperty)
{
return callProperty.GetValue<PersistentCall>();
}
/************************************************************************************************************************/
#region Linked Argument Cache
/************************************************************************************************************************/
/// <summary>Stores all the persistent methods in the current event.</summary>
public void CacheLinkedArguments()
{
PersistentMethodCache.Clear();
if (Event == null || Event._PersistentCalls == null)
return;
for (int i = 0; i < Event._PersistentCalls.Count; i++)
{
var call = Event._PersistentCalls[i];
PersistentMethodCache.Add(call != null ? call.GetMethodSafe() : null);
}
}
/************************************************************************************************************************/
/// <summary>Ensures that any linked parameters remain linked to the correct target index.</summary>
public void UpdateLinkedArguments()
{
if (Event == null ||
PersistentMethodCache.Count == 0)
return;
for (int i = 0; i < Event._PersistentCalls.Count; i++)
{
var call = Event._PersistentCalls[i];
if (call == null)
continue;
for (int j = 0; j < call._PersistentArguments.Length; j++)
{
var argument = call._PersistentArguments[j];
if (argument == null || argument._Type != PersistentArgumentType.ReturnValue)
continue;
var linkedMethod = PersistentMethodCache[argument.ReturnedValueIndex];
if (argument.ReturnedValueIndex < Event._PersistentCalls.Count)
{
var linkedCall = Event._PersistentCalls[argument.ReturnedValueIndex];
if (linkedMethod == (linkedCall != null ? linkedCall.GetMethodSafe() : null))
continue;
}
var index = IndexOfMethod(linkedMethod);
if (index >= 0)
argument.ReturnedValueIndex = index;
}
}
PersistentMethodCache.Clear();
}
/************************************************************************************************************************/
/// <summary>Returns the index of the persistent call that targets the specified `method` or -1 if there is none.</summary>
public int IndexOfMethod(MethodBase method)
{
for (int i = 0; i < Event._PersistentCalls.Count; i++)
{
var call = Event._PersistentCalls[i];
if ((call != null ? call.GetMethodSafe() : null) == method)
{
return i;
}
}
return -1;
}
/************************************************************************************************************************/
/// <summary>Returns the method cached from the persistent call at the specified `index`.</summary>
public MethodBase GetLinkedMethod(int index)
{
if (index >= 0 && index < PersistentMethodCache.Count)
return PersistentMethodCache[index];
else
return null;
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Previous Call Cache
/************************************************************************************************************************/
/// <summary>Tries to get the details of the a parameter or return value of the specified `type`.</summary>
public bool TryGetLinkable(Type type, out int linkIndex, out PersistentArgumentType linkType)
{
if (Event != null)
{
// Parameters.
var parameterTypes = Event.ParameterTypes;
for (int i = 0; i < parameterTypes.Length; i++)
{
if (type.IsAssignableFrom(parameterTypes[i]))
{
linkIndex = i;
linkType = PersistentArgumentType.Parameter;
return true;
}
}
// Return Values.
for (int i = 0; i < PreviousCalls.Count; i++)
{
var method = PreviousCalls[i].GetMethodSafe();
if (method == null)
continue;
if (type.IsAssignableFrom(method.GetReturnType()))
{
linkIndex = i;
linkType = PersistentArgumentType.ReturnValue;
return true;
}
}
}
linkIndex = -1;
linkType = PersistentArgumentType.None;
return false;
}
/************************************************************************************************************************/
/// <summary>Tries to get the details of the a parameter or return value of the current parameter type.</summary>
public bool TryGetLinkable(out int linkIndex, out PersistentArgumentType linkType)
{
if (callParameters != null)
{
return TryGetLinkable(CurrentParameter.ParameterType, out linkIndex, out linkType);
}
else
{
linkIndex = -1;
linkType = PersistentArgumentType.None;
return false;
}
}
/************************************************************************************************************************/
/// <summary>The number of persistent calls that came earlier in the current event.</summary>
public int PreviousCallCount
{
get { return PreviousCalls.Count; }
}
/************************************************************************************************************************/
/// <summary>Returns the persistent call at the specified index in the current event.</summary>
public PersistentCall GetPreviousCall(int index)
{
if (index >= 0 && index < PreviousCalls.Count)
return PreviousCalls[index];
else
return null;
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
/// <summary>Copies the contents of the `other` state to overwrite this one.</summary>
public void CopyFrom(DrawerState other)
{
EventProperty = other.EventProperty;
Event = other.Event;
CallProperty = other.CallProperty;
TargetProperty = other.TargetProperty;
MethodNameProperty = other.MethodNameProperty;
PersistentArgumentsProperty = other.PersistentArgumentsProperty;
callIndex = other.callIndex;
call = other.call;
callParameters = other.callParameters;
parameterIndex = other.parameterIndex;
PreviousCalls.Clear();
PreviousCalls.AddRange(other.PreviousCalls);
}
/************************************************************************************************************************/
/// <summary>Clears all the details of this state.</summary>
public void Clear()
{
EventProperty = null;
Event = null;
CallProperty = null;
TargetProperty = null;
MethodNameProperty = null;
PersistentArgumentsProperty = null;
callIndex = -1;
call = null;
callParameters = null;
parameterIndex = 0;
PreviousCalls.Clear();
}
/************************************************************************************************************************/
}
}
#endif

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 01c9b3f0af8a8b642b1c6578cfdcf416
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2ebc5b36ea6721c428b0c87c8307e068
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,728 @@
// UltEvents // Copyright 2021 Kybernetik //
// Copied from Kybernetik.Core.
#if UNITY_EDITOR
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using UnityEditor;
using UnityEngine;
namespace UltEvents.Editor
{
/// <summary>[Editor-Only] Allows you to draw GUI fields which can be used to pick an object from a list.</summary>
public static class ObjectPicker
{
/************************************************************************************************************************/
#region Main Drawing Methods
/************************************************************************************************************************/
/// <summary>Draws a field which lets you pick an object from a list and returns the selected object.</summary>
public static T Draw<T>(Rect area, T selected, Func<List<T>> getOptions, int suggestions, Func<T, GUIContent> getLabel, Func<T> getDragAndDrop,
GUIStyle style)
{
var id = CheckCommand(ref selected);
if (GUI.Button(area, getLabel(selected), style))
ObjectPickerWindow.Show(id, selected, getOptions(), suggestions, getLabel);
CheckDragAndDrop(area, ref selected, getOptions, getDragAndDrop);
return selected;
}
/// <summary>Draws a field which lets you pick an object from a list and returns the selected object.</summary>
public static T Draw<T>(Rect area, T selected, Func<List<T>> getOptions, int suggestions, Func<T, GUIContent> getLabel, Func<T> getDragAndDrop)
{
return Draw(area, selected, getOptions, suggestions, getLabel, getDragAndDrop, InternalGUI.TypeButtonStyle);
}
/************************************************************************************************************************/
/// <summary>Draws a field (using GUILayout) which lets you pick an object from a list and returns the selected object.</summary>
public static T DrawLayout<T>(T selected, Func<List<T>> getOptions, int suggestions, Func<T, GUIContent> getLabel, Func<T> getDragAndDrop,
GUIStyle style, params GUILayoutOption[] layoutOptions)
{
var id = CheckCommand(ref selected);
if (GUILayout.Button(getLabel(selected), style, layoutOptions))
ObjectPickerWindow.Show(id, selected, getOptions(), suggestions, getLabel);
CheckDragAndDrop(GUILayoutUtility.GetLastRect(), ref selected, getOptions, getDragAndDrop);
return selected;
}
/// <summary>Draws a field (using GUILayout) which lets you pick an object from a list and returns the selected object.</summary>
public static T DrawLayout<T>(T selected, Func<List<T>> getOptions, int suggestions, Func<T, GUIContent> getLabel, Func<T> getDragAndDrop,
params GUILayoutOption[] layoutOptions)
{
return DrawLayout(selected, getOptions, suggestions, getLabel, getDragAndDrop, InternalGUI.TypeButtonStyle, layoutOptions);
}
/************************************************************************************************************************/
/// <summary>
/// Draws a field (as an inspector field using GUILayout) which lets you pick an object from a list and returns
/// the selected object.
/// </summary>
public static T DrawEditorLayout<T>(GUIContent label, T selected, Func<List<T>> getOptions, int suggestions,
Func<T, GUIContent> getLabel, Func<T> getDragAndDrop, GUIStyle style, params GUILayoutOption[] layoutOptions)
{
GUILayout.BeginHorizontal();
{
GUILayout.Label(label, GUILayout.Width(EditorGUIUtility.labelWidth - 4));
selected = DrawLayout(selected, getOptions, suggestions, getLabel, getDragAndDrop, style, layoutOptions);
}
GUILayout.EndHorizontal();
return selected;
}
/// <summary>
/// Draws a field (as an inspector field using GUILayout) which lets you pick an object from a list and returns
/// the selected object.
/// </summary>
public static T DrawEditorLayout<T>(GUIContent label, T selected, Func<List<T>> getOptions, int suggestions,
Func<T, GUIContent> getLabel, Func<T> getDragAndDrop, params GUILayoutOption[] options)
{
return DrawEditorLayout(label, selected, getOptions, suggestions, getLabel, getDragAndDrop, InternalGUI.TypeButtonStyle, options);
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Type Field
/************************************************************************************************************************/
/// <summary>Draws a field which lets you pick a <see cref="Type"></see> from a list and returns the selected type.</summary>
public static Type DrawTypeField(Rect area, Type selected, Func<List<Type>> getOptions, int suggestions, GUIStyle style)
{
return Draw(area, selected, getOptions, suggestions,
getLabel: (type) => new GUIContent(type != null ? type.GetNameCS() : "null"),
getDragAndDrop: () => DragAndDrop.objectReferences[0].GetType(),
style: style);
}
/************************************************************************************************************************/
/// <summary>Draws a field which lets you pick an asset <see cref="Type"></see> from a list and returns the selected type.</summary>
public static Type DrawAssetTypeField(Rect area, Type selected, Func<List<Type>> getOptions, int suggestions, GUIStyle style)
{
return Draw(area, selected, getOptions, suggestions,
getLabel: (type) => new GUIContent(type != null ? type.GetNameCS() : "null", AssetPreview.GetMiniTypeThumbnail(type)),
getDragAndDrop: () => DragAndDrop.objectReferences[0].GetType(),
style: style);
}
/************************************************************************************************************************/
/// <summary>Draws a field which lets you pick a <see cref="Type"></see> from a list and returns the selected <see cref="Type.AssemblyQualifiedName"/>.</summary>
public static string DrawTypeField(Rect area, string selectedTypeName, Func<List<Type>> getOptions, int suggestions, GUIStyle style)
{
var selected = Type.GetType(selectedTypeName);
selected = Draw(area, selected, getOptions, suggestions,
getLabel: (type) => new GUIContent(type != null ? type.GetNameCS() : "No Type Selected"),
getDragAndDrop: () => DragAndDrop.objectReferences[0].GetType(),
style: style);
return selected != null ? selected.AssemblyQualifiedName : null;
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Utils
/************************************************************************************************************************/
/// <summary>
/// Removes any duplicates of the first few elements in `options` (from 0 to `suggestions`) from anywhere later
/// in the list.
/// </summary>
public static void RemoveDuplicateSuggestions<T>(List<T> options, int suggestions) where T : class
{
for (int i = options.Count - 1; i >= suggestions; i--)
{
var obj = options[i];
for (int j = 0; j < suggestions; j++)
{
if (obj == options[j])
{
options.RemoveAt(j);
break;
}
}
}
}
/************************************************************************************************************************/
private static int CheckCommand<T>(ref T selected)
{
var id = GUIUtility.GetControlID(FocusType.Passive);
ObjectPickerWindow.TryGetPickedObject(id, ref selected);
return id;
}
/************************************************************************************************************************/
private static void CheckDragAndDrop<T>(Rect area, ref T selected, Func<List<T>> getOptions, Func<T> getDragAndDrop)
{
var currentEvent = Event.current;
if (DragAndDrop.objectReferences.Length == 1 && area.Contains(currentEvent.mousePosition))
{
var drop = getDragAndDrop();
// If the dragged object is a valid type, continue.
if (!getOptions().Contains(drop))
return;
if (currentEvent.type == EventType.DragUpdated || currentEvent.type == EventType.MouseDrag)
{
DragAndDrop.visualMode = DragAndDropVisualMode.Link;
}
else if (currentEvent.type == EventType.DragPerform)
{
selected = drop;
DragAndDrop.AcceptDrag();
GUI.changed = true;
}
}
}
/************************************************************************************************************************/
private static class InternalGUI
{
public static readonly GUIStyle
TypeButtonStyle;
static InternalGUI()
{
TypeButtonStyle = new GUIStyle(EditorStyles.miniButton)
{
alignment = TextAnchor.MiddleLeft
};
}
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
}
/************************************************************************************************************************/
internal sealed class ObjectPickerWindow : EditorWindow
{
/************************************************************************************************************************/
private static int _FieldID;
private static bool _HasPickedObject;
private static object _PickedObject;
private readonly List<GUIContent>
Labels = new List<GUIContent>();
private readonly List<GUIContent>
SearchedLabels = new List<GUIContent>();
private readonly List<object>
SearchedObjects = new List<object>();
[NonSerialized]
private object _SelectedObject;
[NonSerialized]
private IList _Objects;
[NonSerialized]
private int _Suggestions;
[NonSerialized]
private int _LabelWidthCalculationProgress;
[NonSerialized]
private float _MaxLabelWidth;
[NonSerialized]
private string _SearchText = "";
[NonSerialized]
private Vector2 _ScrollPosition;
/************************************************************************************************************************/
private bool HasSearchText
{
get { return !string.IsNullOrEmpty(_SearchText); }
}
/************************************************************************************************************************/
public static void Show<T>(int fieldID, T selected, List<T> objects, int suggestions, Func<T, GUIContent> getLabel)
{
if (objects == null || objects.Count == 0)
{
Debug.LogError("'objects' list is null or empty.");
return;
}
_FieldID = fieldID;
_HasPickedObject = false;
var window = CreateInstance<ObjectPickerWindow>();
window.titleContent = new GUIContent("Pick a " + typeof(T).GetNameCS());
window.minSize = new Vector2(112, 100);
if (window.Labels.Capacity < objects.Count)
window.Labels.Capacity = objects.Count;
for (int i = 0; i < objects.Count; i++)
window.Labels.Add(getLabel(objects[i]));
//Debug.LogTemp("Showing Object Picker Window: " + window._Labels.DeepToString());
window._SelectedObject = selected;
window._Objects = objects;
window._Suggestions = suggestions;
// Auto-Scroll to the selected object.
if (selected != null)
{
object sel = selected;
for (int i = 0; i < window._Objects.Count; i++)
{
if (sel == window._Objects[i])
{
window._ScrollPosition = new Vector2(0, i * InternalGUI.LabelHeight);
break;
}
}
}
//window._Objects.LogErrorIfModified("the '" + nameof(objects) + "' list passed into " + Reflection.GetCallingMethod(2).GetNameCS());
window.ShowAuxWindow();
}
/************************************************************************************************************************/
public static void TryGetPickedObject<T>(int fieldID, ref T picked)
{
if (_HasPickedObject && _FieldID == fieldID)
{
picked = (T)_PickedObject;
_PickedObject = null;
_HasPickedObject = false;
GUI.changed = true;
}
}
/************************************************************************************************************************/
private void PickAndClose()
{
_PickedObject = _SelectedObject;
_HasPickedObject = true;
Close();
UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
}
/************************************************************************************************************************/
private void OnGUI()
{
switch (Event.current.type)
{
case EventType.MouseMove:
case EventType.Layout:
case EventType.DragUpdated:
case EventType.DragPerform:
case EventType.DragExited:
case EventType.Ignore:
case EventType.Used:
case EventType.ValidateCommand:
case EventType.ExecuteCommand:
case EventType.ContextClick:
return;
default:
break;
}
if (CheckInput())
{
Event.current.Use();
return;
}
UpdateLabelWidthCalculation();
var area = new Rect(0, 0, position.width, position.height);
DrawSearchBar(ref area);
area.yMax = position.height;
var viewRect = CalculateViewRect(area.height);
// Selection List.
_ScrollPosition = GUI.BeginScrollView(area, _ScrollPosition, viewRect);
{
// Figure out how many fields are actually visible.
int firstVisibleField, lastVisibleField;
DetermineVisibleRange(out firstVisibleField, out lastVisibleField);
if (HasSearchText)// Active Search.
{
DrawSearchedOptions(viewRect, firstVisibleField, lastVisibleField);
}
else// No Search.
{
DrawAllOptions(viewRect, firstVisibleField, lastVisibleField);
}
}
GUI.EndScrollView(true);
}
/************************************************************************************************************************/
private void UpdateLabelWidthCalculation()
{
if (_LabelWidthCalculationProgress < Labels.Count)
{
var calculationCount = 0;
do
{
var label = Labels[_LabelWidthCalculationProgress];
var width = InternalGUI.ButtonStyle.CalcSize(label).x;
if (_MaxLabelWidth < width)
_MaxLabelWidth = width;
}
while (++_LabelWidthCalculationProgress < Labels.Count && calculationCount++ < 100);
Repaint();
}
}
/************************************************************************************************************************/
private bool CheckInput()
{
var currentEvent = Event.current;
if (currentEvent.type == EventType.KeyUp)
{
switch (currentEvent.keyCode)
{
case KeyCode.Return:
PickAndClose();
return true;
case KeyCode.Escape:
Close();
return true;
case KeyCode.UpArrow:
OffsetSelectedIndex(-1);
return true;
case KeyCode.DownArrow:
OffsetSelectedIndex(1);
return true;
default:
break;
}
}
return false;
}
/************************************************************************************************************************/
private void DrawSearchBar(ref Rect area)
{
area.height = InternalGUI.SearchBarHeight;
GUI.BeginGroup(area, EditorStyles.toolbar);
{
area.x += 2;
area.y += 2;
area.width -= InternalGUI.SearchBarEndStyle.fixedWidth + 4;
GUI.SetNextControlName("SearchFilter");
EditorGUI.BeginChangeCheck();
var searchText = GUI.TextField(area, _SearchText, InternalGUI.SearchBarStyle);
if (EditorGUI.EndChangeCheck())
OnSearchTextChanged(searchText);
EditorGUI.FocusTextInControl("SearchFilter");
area.x = area.xMax;
area.width = InternalGUI.SearchBarEndStyle.fixedWidth;
if (HasSearchText)
{
if (GUI.Button(area, "", InternalGUI.SearchBarCancelStyle))
{
_SearchText = "";
}
}
else GUI.Box(area, "", InternalGUI.SearchBarEndStyle);
}
GUI.EndGroup();
area.x = 0;
area.width = position.width;
area.y += area.height;
}
/************************************************************************************************************************/
private void OnSearchTextChanged(string text)
{
if (string.IsNullOrEmpty(text))
{
SearchedLabels.Clear();
SearchedObjects.Clear();
}
// If the search text starts the same as before, it will include only a subset of the previous options.
// So we can just remove objects from the previous search list instead of checking the full list again.
else if (SearchedLabels.Count > 0 && text.StartsWith(_SearchText))
{
for (int i = SearchedLabels.Count - 1; i >= 0; i--)
{
if (!IsVisibleInSearch(text, SearchedLabels[i].text))
{
SearchedLabels.RemoveAt(i);
SearchedObjects.RemoveAt(i);
}
}
}
// Otherwise clear the search list and re-gather any visible objects from the full list.
else
{
SearchedLabels.Clear();
SearchedObjects.Clear();
for (int i = 0; i < Labels.Count; i++)
{
var label = Labels[i];
if (IsVisibleInSearch(text, label.text))
{
SearchedLabels.Add(label);
SearchedObjects.Add(_Objects[i]);
}
}
}
_SearchText = text;
if (!SearchedObjects.Contains(_SelectedObject))
_SelectedObject = SearchedObjects.Count > 0 ? SearchedObjects[0] : null;
}
private static bool IsVisibleInSearch(string search, string text)
{
return CultureInfo.CurrentCulture.CompareInfo.IndexOf(text, search, CompareOptions.IgnoreCase) >= 0;
}
/************************************************************************************************************************/
private Rect CalculateViewRect(float height)
{
var area = new Rect();
if (HasSearchText)
{
area.height = InternalGUI.LabelHeight * SearchedLabels.Count;
}
else
{
area.height = InternalGUI.LabelHeight * Labels.Count;
if (_Suggestions > 0)
area.height += InternalGUI.HeaddingStyle.fixedHeight * 2;
}
if (_MaxLabelWidth < position.width)
{
area.width = position.width;
if (area.height > height)
area.width -= 16;
}
else area.width = _MaxLabelWidth;
return area;
}
/************************************************************************************************************************/
private void DetermineVisibleRange(out int firstVisibleField, out int lastVisibleField)
{
var top = _ScrollPosition.y;
var bottom = top + position.height - InternalGUI.SearchBarHeight;
if (_Suggestions > 0)
{
top -= InternalGUI.HeaddingStyle.fixedHeight * 2;
bottom += InternalGUI.HeaddingStyle.fixedHeight;
}
firstVisibleField = Mathf.Max(0, (int)(top / InternalGUI.LabelHeight));
lastVisibleField = Mathf.Min(Labels.Count, Mathf.CeilToInt(bottom / InternalGUI.LabelHeight));
}
/************************************************************************************************************************/
private void DrawAllOptions(Rect area, int firstVisibleField, int lastVisibleField)
{
if (_Suggestions == 0 || _Suggestions >= Labels.Count)
{
area.y = firstVisibleField * InternalGUI.LabelHeight;
DrawRange(ref area, Labels, _Objects, firstVisibleField, lastVisibleField);
}
else
{
area.height = InternalGUI.HeaddingStyle.fixedHeight;
GUI.Label(area, "Suggestions", InternalGUI.HeaddingStyle);
area.y = area.yMax + firstVisibleField * InternalGUI.LabelHeight;
DrawRange(ref area, Labels, _Objects, firstVisibleField, Mathf.Min(lastVisibleField, _Suggestions));
area.height = InternalGUI.HeaddingStyle.fixedHeight;
GUI.Label(area, "Other Options", InternalGUI.HeaddingStyle);
area.y = area.yMax;
DrawRange(ref area, Labels, _Objects, Mathf.Max(_Suggestions, firstVisibleField), lastVisibleField);
}
}
/************************************************************************************************************************/
private void DrawSearchedOptions(Rect area, int firstVisibleField, int lastVisibleField)
{
area.y = firstVisibleField * InternalGUI.LabelHeight;
DrawRange(ref area, SearchedLabels, SearchedObjects, firstVisibleField, lastVisibleField);
}
/************************************************************************************************************************/
private void DrawRange(ref Rect area, List<GUIContent> labels, IList objects, int start, int end)
{
area.height = InternalGUI.LabelHeight;
if (end > labels.Count)
end = labels.Count;
for (; start < end; start++)
{
DrawOption(area, labels, objects, start);
area.y = area.yMax;
}
}
/************************************************************************************************************************/
private void DrawOption(Rect area, List<GUIContent> labels, IList objects, int index)
{
var obj = objects[index];
var wasOn = obj == _SelectedObject;
var isOn = GUI.Toggle(area, wasOn, labels[index], wasOn ? InternalGUI.SelectedButtonStyle : InternalGUI.ButtonStyle);
if (isOn != wasOn)
{
if (wasOn)
{
PickAndClose();
}
else if (isOn)
{
_SelectedObject = obj;
}
}
}
/************************************************************************************************************************/
private void Update()
{
if (focusedWindow != this)
Close();
}
/************************************************************************************************************************/
private void OffsetSelectedIndex(int offset)
{
var objects = HasSearchText ? SearchedObjects : _Objects;
if (objects.Count == 0)
return;
var index = objects.IndexOf(_SelectedObject);
if (index >= 0)
_SelectedObject = objects[Mathf.Clamp(index + offset, 0, objects.Count)];
else
_SelectedObject = objects[0];
}
/************************************************************************************************************************/
private static class InternalGUI
{
public static readonly GUIStyle
SearchBarStyle;
public static readonly GUIStyle
SearchBarEndStyle;
public static readonly GUIStyle
SearchBarCancelStyle;
public static readonly GUIStyle
HeaddingStyle;
public static readonly GUIStyle
ButtonStyle;
public static readonly GUIStyle
SelectedButtonStyle;
public static float SearchBarHeight
{
get { return EditorStyles.toolbar.fixedHeight; }
}
public static float LabelHeight
{
get { return ButtonStyle.fixedHeight; }
}
static InternalGUI()
{
SearchBarStyle = GUI.skin.FindStyle("ToolbarSeachTextField");
SearchBarEndStyle = GUI.skin.FindStyle("ToolbarSeachCancelButtonEmpty");
SearchBarCancelStyle = GUI.skin.FindStyle("ToolbarSeachCancelButton");
HeaddingStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 12,
alignment = TextAnchor.MiddleLeft,
fixedHeight = 22
};
ButtonStyle = new GUIStyle(EditorStyles.toolbarButton)
{
alignment = TextAnchor.MiddleLeft,
fontSize = 12
};
SelectedButtonStyle = new GUIStyle(ButtonStyle)
{
fontStyle = FontStyle.Bold
};
}
}
/************************************************************************************************************************/
}
}
#endif

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 657c235856cafdd41a65047f9688b0cb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,689 @@
// UltEvents // Copyright 2021 Kybernetik //
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace UltEvents.Editor
{
[CustomPropertyDrawer(typeof(PersistentArgument), true)]
internal sealed class PersistentArgumentDrawer : PropertyDrawer
{
/************************************************************************************************************************/
private const float
SpecialModeToggleWidth = PersistentCallDrawer.RemoveButtonWidth;
/************************************************************************************************************************/
public override void OnGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
{
int linkIndex;
PersistentArgumentType linkType;
if (DrawerState.Current.TryGetLinkable(out linkIndex, out linkType))
area.width -= SpecialModeToggleWidth;
var wideMode = EditorGUIUtility.wideMode;
EditorGUIUtility.wideMode = true;
var typeProperty = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Type);
switch ((PersistentArgumentType)typeProperty.enumValueIndex)
{
case PersistentArgumentType.None: DoErrorMessageGUI(area, argumentProperty, label, "Error: argument type not set"); break;
case PersistentArgumentType.Bool: DoBoolGUI(area, argumentProperty, label); break;
case PersistentArgumentType.String: DoStringGUI(area, argumentProperty, label); break;
case PersistentArgumentType.Int: DoIntGUI(area, argumentProperty, label); break;
case PersistentArgumentType.Enum: DoEnumGUI(area, argumentProperty, label); break;
case PersistentArgumentType.Float: DoFloatGUI(area, argumentProperty, label); break;
case PersistentArgumentType.Vector2: DoVector2GUI(area, argumentProperty, label); break;
case PersistentArgumentType.Vector3: DoVector3GUI(area, argumentProperty, label); break;
case PersistentArgumentType.Vector4: DoVector4GUI(area, argumentProperty, label); break;
case PersistentArgumentType.Quaternion: DoQuaternionGUI(area, argumentProperty, label); break;
case PersistentArgumentType.Color: DoColorGUI(area, argumentProperty, label); break;
case PersistentArgumentType.Color32: DoColor32GUI(area, argumentProperty, label); break;
case PersistentArgumentType.Rect: DoRectGUI(area, argumentProperty, label); break;
case PersistentArgumentType.Object: DoObjectGUI(area, argumentProperty, label); break;
case PersistentArgumentType.Parameter:
case PersistentArgumentType.ReturnValue:
DoLinkedValueGUI(area, argumentProperty, label); break;
default: DoErrorMessageGUI(area, argumentProperty, label, "Error: unexpected argument type " + (PersistentArgumentType)typeProperty.enumValueIndex); break;
}
EditorGUIUtility.wideMode = wideMode;
DoLinkModeToggleGUI(area, argumentProperty, linkIndex, linkType);
}
/************************************************************************************************************************/
private static void DoErrorMessageGUI(Rect area, SerializedProperty argumentProperty, GUIContent label, string message)
{
label = EditorGUI.BeginProperty(area, label, argumentProperty);
EditorGUI.PrefixLabel(area, label);
area.xMin += EditorGUIUtility.labelWidth;
var color = GUI.color;
GUI.color = Color.red;
GUI.Label(area, message, EditorStyles.whiteLabel);
GUI.color = color;
EditorGUI.EndProperty();
}
/************************************************************************************************************************/
private static void DoBoolGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
{
var i = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Int);
label = EditorGUI.BeginProperty(area, label, i);
EditorGUI.BeginChangeCheck();
var value = EditorGUI.Toggle(area, label, i.intValue != 0);
if (EditorGUI.EndChangeCheck())
{
i.intValue = value ? 1 : 0;
}
EditorGUI.EndProperty();
}
/************************************************************************************************************************/
private static void DoStringGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
{
var s = argumentProperty.FindPropertyRelative(Names.PersistentArgument.String);
label = EditorGUI.BeginProperty(area, label, s);
EditorGUI.BeginChangeCheck();
var value = EditorGUI.TextField(area, label, s.stringValue);
if (EditorGUI.EndChangeCheck())
{
s.stringValue = value;
}
EditorGUI.EndProperty();
}
/************************************************************************************************************************/
private static void DoIntGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
{
var i = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Int);
label = EditorGUI.BeginProperty(area, label, i);
EditorGUI.BeginChangeCheck();
var value = EditorGUI.IntField(area, label, i.intValue);
if (EditorGUI.EndChangeCheck())
{
i.intValue = value;
}
EditorGUI.EndProperty();
}
/************************************************************************************************************************/
#region Enum
/************************************************************************************************************************/
private static void DoEnumGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
{
var enumType = argumentProperty.GetValue<PersistentArgument>().SystemType;
if (enumType == null)
{
DoErrorMessageGUI(area, argumentProperty, label, "Error: enum type not set");
return;
}
var i = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Int);
label = EditorGUI.BeginProperty(area, label, argumentProperty);
EditorGUI.BeginChangeCheck();
int value;
if (enumType.IsDefined(typeof(FlagsAttribute), true))
{
value = DrawEnumMaskField(area, label, i.intValue, enumType);
}
else
{
value = Convert.ToInt32(EditorGUI.EnumPopup(area, label, (Enum)Enum.ToObject(enumType, i.intValue)));
}
if (EditorGUI.EndChangeCheck())
{
i.intValue = value;
}
EditorGUI.EndProperty();
}
/************************************************************************************************************************/
private static Dictionary<Type, string[]> _Names;
private static Dictionary<Type, int[]> _Values;
/// <summary>
/// Draw a field with a dropdown box for selecting values in a flags enum.
/// <para></para>
/// This method works properly for some enum configurations not supported by EditorGUI.EnumMaskField or EditorGUI.EnumMaskPopup.
/// <para></para>
/// This method only supports enums with int as their underlying type.
/// </summary>
public static int DrawEnumMaskField(Rect area, GUIContent label, int enumValue, Type enumType)
{
if (_Names == null)
{
_Names = new Dictionary<Type, string[]>();
_Values = new Dictionary<Type, int[]>();
}
string[] names;
if (!_Names.TryGetValue(enumType, out names))
{
names = Enum.GetNames(enumType);
_Names.Add(enumType, names);
}
int[] values;
if (!_Values.TryGetValue(enumType, out values))
{
values = Enum.GetValues(enumType) as int[];
_Values.Add(enumType, values);
}
var maskValue = 0;
for (int i = 0; i < values.Length; i++)
{
var v = values[i];
if (v != 0)
{
if ((enumValue & v) == v)
maskValue |= 1 << i;
}
else if (enumValue == 0)
{
maskValue |= 1 << i;
}
}
EditorGUI.BeginChangeCheck();
var newMaskVal = EditorGUI.MaskField(area, label, maskValue, names);
if (EditorGUI.EndChangeCheck())
{
var changes = maskValue ^ newMaskVal;
for (int i = 0; i < values.Length; i++)
{
if ((changes & (1 << i)) != 0)
{
if ((newMaskVal & (1 << i)) != 0)
{
var v = values[i];
if (v == 0)// special case: if "0" is set, just set the value to 0.
{
enumValue = 0;
break;
}
else
{
enumValue |= v;
}
}
else
{
enumValue &= ~values[i];
}
}
}
}
return enumValue;
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
private static void DoFloatGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
{
var x = argumentProperty.FindPropertyRelative(Names.PersistentArgument.X);
label = EditorGUI.BeginProperty(area, label, x);
EditorGUI.BeginChangeCheck();
var value = EditorGUI.FloatField(area, label, x.floatValue);
if (EditorGUI.EndChangeCheck())
{
x.floatValue = value;
}
EditorGUI.EndProperty();
}
/************************************************************************************************************************/
private static GUIContent[] _Vector2Labels;
private static void DoVector2GUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
{
if (_Vector2Labels == null)
{
_Vector2Labels = new GUIContent[]
{
new GUIContent("X"),
new GUIContent("Y"),
};
}
var x = argumentProperty.FindPropertyRelative(Names.PersistentArgument.X);
label = EditorGUI.BeginProperty(area, label, argumentProperty);
EditorGUI.MultiPropertyField(area, _Vector2Labels, x, label);
EditorGUI.EndProperty();
}
/************************************************************************************************************************/
private static GUIContent[] _Vector3Labels;
private static void DoVector3GUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
{
if (_Vector3Labels == null)
{
_Vector3Labels = new GUIContent[]
{
new GUIContent("X"),
new GUIContent("Y"),
new GUIContent("Z"),
};
}
var x = argumentProperty.FindPropertyRelative(Names.PersistentArgument.X);
label = EditorGUI.BeginProperty(area, label, argumentProperty);
EditorGUI.MultiPropertyField(area, _Vector3Labels, x, label);
EditorGUI.EndProperty();
}
/************************************************************************************************************************/
private static GUIContent[] _Vector4Labels;
private static void DoVector4GUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
{
if (_Vector4Labels == null)
{
_Vector4Labels = new GUIContent[]
{
new GUIContent("X"),
new GUIContent("Y"),
new GUIContent("Z"),
new GUIContent("W"),
};
}
var x = argumentProperty.FindPropertyRelative(Names.PersistentArgument.X);
label = EditorGUI.BeginProperty(area, label, argumentProperty);
EditorGUI.MultiPropertyField(area, _Vector4Labels, x, label);
EditorGUI.EndProperty();
}
/************************************************************************************************************************/
private static void DoQuaternionGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
{
EditorGUI.BeginChangeCheck();
DoVector3GUI(area, argumentProperty, label);
if (EditorGUI.EndChangeCheck())
{
var x = argumentProperty.FindPropertyRelative(Names.PersistentArgument.X);
var y = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Y);
var z = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Z);
x.floatValue %= 360;
y.floatValue %= 360;
z.floatValue %= 360;
}
}
/************************************************************************************************************************/
private static void DoColorGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
{
var x = argumentProperty.FindPropertyRelative(Names.PersistentArgument.X);
var y = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Y);
var z = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Z);
var w = argumentProperty.FindPropertyRelative(Names.PersistentArgument.W);
label = EditorGUI.BeginProperty(area, label, argumentProperty);
EditorGUI.BeginChangeCheck();
var value = EditorGUI.ColorField(area, label, new Color(x.floatValue, y.floatValue, z.floatValue, w.floatValue));
if (EditorGUI.EndChangeCheck())
{
x.floatValue = value.r;
y.floatValue = value.g;
z.floatValue = value.b;
w.floatValue = value.a;
}
EditorGUI.EndProperty();
}
/************************************************************************************************************************/
private static void DoColor32GUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
{
var i = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Int);
label = EditorGUI.BeginProperty(area, label, argumentProperty);
EditorGUI.BeginChangeCheck();
var intValue = i.intValue;
var value = EditorGUI.ColorField(area, label,
new Color32((byte)(intValue), (byte)(intValue >> 8), (byte)(intValue >> 16), (byte)(intValue >> 24)));
if (EditorGUI.EndChangeCheck())
{
var value32 = (Color32)value;
i.intValue = value32.r | (value32.g << 8) | (value32.b << 16) | (value32.a << 24);
}
EditorGUI.EndProperty();
}
/************************************************************************************************************************/
private static GUIContent[] _RectLabels;
private static void DoRectGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
{
if (_RectLabels == null)
{
_RectLabels = new GUIContent[]
{
new GUIContent("X"),
new GUIContent("Y"),
new GUIContent("W"),
new GUIContent("H"),
};
}
var x = argumentProperty.FindPropertyRelative(Names.PersistentArgument.X);
label = EditorGUI.BeginProperty(area, label, argumentProperty);
EditorGUI.MultiPropertyField(area, _RectLabels, x, label);
EditorGUI.EndProperty();
}
/************************************************************************************************************************/
private static void DoObjectGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
{
var o = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Object);
label = EditorGUI.BeginProperty(area, label, o);
EditorGUI.BeginChangeCheck();
var type = argumentProperty.GetValue<PersistentArgument>().SystemType ?? typeof(UnityEngine.Object);
var value = EditorGUI.ObjectField(area, label, o.objectReferenceValue, type, true);
if (EditorGUI.EndChangeCheck())
{
o.objectReferenceValue = value;
}
EditorGUI.EndProperty();
}
/************************************************************************************************************************/
private static void DoLinkedValueGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
{
var color = GUI.color;
label = EditorGUI.BeginProperty(area, label, argumentProperty);
EditorGUI.PrefixLabel(area, label);
area.xMin += EditorGUIUtility.labelWidth;
var argument = argumentProperty.GetValue<PersistentArgument>();
var callIndex = argument._Int;
var argumentType = argument.SystemType;
if (argumentType == null)
{
GUI.color = PersistentCallDrawer.ErrorFieldColor;
GUI.Label(area, "Unable to determine argument type");
}
else if (DrawerState.Current.Event != null)
{
switch (argument.Type)
{
case PersistentArgumentType.Parameter:
label.text = "Parameter " + callIndex;
var parameterTypes = DrawerState.Current.Event.ParameterTypes;
var parameters = DrawerState.Current.Event.Parameters;
if (callIndex < 0 || callIndex >= parameterTypes.Length)
{
GUI.color = PersistentCallDrawer.ErrorFieldColor;
label.tooltip = "Parameter link index out of range";
}
else
{
var parameterType = parameterTypes[callIndex];
label.text += " (" + parameterType.GetNameCS(false);
if (parameters != null)
label.text += " " + parameters[callIndex].Name;
label.text += ")";
if (!argumentType.IsAssignableFrom(parameterType))
{
GUI.color = PersistentCallDrawer.ErrorFieldColor;
label.tooltip = "Incompatible parameter type";
}
}
break;
case PersistentArgumentType.ReturnValue:
label.text = "Return Value " + callIndex + ": ";
var linkedMethod = DrawerState.Current.GetLinkedMethod(callIndex);
if (linkedMethod == null)
{
label.text += "(no method set)";
GUI.color = PersistentCallDrawer.ErrorFieldColor;
}
else
{
label.text += MethodSelectionMenu.GetMethodSignature(linkedMethod, true);
if (DrawerState.Current.callIndex >= 0 && DrawerState.Current.callIndex <= callIndex)
{
GUI.color = PersistentCallDrawer.ErrorFieldColor;
label.tooltip = "The linked method must be called before this argument can retrieve its return value";
}
else if (!argumentType.IsAssignableFrom(linkedMethod.GetReturnType()))
{
GUI.color = PersistentCallDrawer.ErrorFieldColor;
label.tooltip = "Return type is incompatible with argument type";
}
}
break;
}
if (GUI.Button(area, label, PersistentCallDrawer.PopupButtonStyle))
{
if (Event.current.button == 0)
ShowLinkMenu(area, argumentProperty, argumentType, callIndex, argument.Type);
}
}
EditorGUI.EndProperty();
GUI.color = color;
}
/************************************************************************************************************************/
private static void ShowLinkMenu(Rect area, SerializedProperty argumentProperty, Type systemType, int linkIndex, PersistentArgumentType linkType)
{
var typeProperty = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Type);
var intProperty = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Int);
var menu = new GenericMenu();
menu.AddDisabledItem(new GUIContent("Link to " + systemType.GetNameCS()));
// Parameters.
var parameters = DrawerState.Current.Event.Parameters;
for (int i = 0; i < DrawerState.Current.Event.ParameterTypes.Length; i++)
{
var parameterType = DrawerState.Current.Event.ParameterTypes[i];
if (!systemType.IsAssignableFrom(parameterType))
continue;
var content = parameters == null ?
new GUIContent(string.Concat("Parameter ", i.ToString(), " (", parameterType.GetNameCS(false), ")")) :
new GUIContent(string.Concat("Parameter ", i.ToString(), " (", parameterType.GetNameCS(false), " ", parameters[i].Name, ")"));
var on = i == linkIndex && linkType == PersistentArgumentType.Parameter;
var index = i;
menu.AddItem(content, on, () =>
{
typeProperty.enumValueIndex = (int)PersistentArgumentType.Parameter;
intProperty.intValue = index;
argumentProperty.serializedObject.ApplyModifiedProperties();
});
}
// Returned Values.
for (int i = 0; i < DrawerState.Current.PreviousCallCount; i++)
{
var method = DrawerState.Current.GetPreviousCall(i).GetMethodSafe();
if (method == null || !systemType.IsAssignableFrom(method.GetReturnType()))
continue;
var content = new GUIContent(string.Concat("Returned Value ", i.ToString(), " (", MethodSelectionMenu.GetMethodSignature(method, true), ")"));
var on = i == linkIndex && linkType == PersistentArgumentType.ReturnValue;
var index = i;
menu.AddItem(content, on, () =>
{
typeProperty.enumValueIndex = (int)PersistentArgumentType.ReturnValue;
intProperty.intValue = index;
argumentProperty.serializedObject.ApplyModifiedProperties();
});
}
menu.DropDown(area);
}
/************************************************************************************************************************/
private static GUIContent _LinkToggleContent = new GUIContent("∞", "Link to Parameter or Returned Value");
private static GUIStyle _LinkToggleStyle;
private static void DoLinkModeToggleGUI(Rect area, SerializedProperty argumentProperty, int linkIndex, PersistentArgumentType linkType)
{
if (linkIndex < 0)
return;
if (_LinkToggleStyle == null)
{
_LinkToggleStyle = new GUIStyle(EditorStyles.miniButton)
{
#if UNITY_2019_3_OR_NEWER
padding = new RectOffset(0, -1, 0, 1),
#else
padding = new RectOffset(0, 0, 0, 1),
#endif
fontSize = 12,
};
}
area.x += area.width + 2;
area.width = SpecialModeToggleWidth - 2;
var currentArgument = DrawerState.Current.call._PersistentArguments[DrawerState.Current.parameterIndex];
var wasLink =
currentArgument.Type == PersistentArgumentType.Parameter ||
currentArgument.Type == PersistentArgumentType.ReturnValue;
if (wasLink != GUI.Toggle(area, wasLink, _LinkToggleContent, _LinkToggleStyle))
{
argumentProperty.ModifyValues<PersistentArgument>((argument) =>
{
if (wasLink)
{
// Revert to normal mode.
argument.SystemType = argument.SystemType;
var parameter = DrawerState.Current.CurrentParameter;
if ((parameter.Attributes & ParameterAttributes.HasDefault) == ParameterAttributes.HasDefault)
argument.Value = parameter.DefaultValue;
}
else
{
// Link to the specified return value.
var argumentType = argument.Type;
argument.Type = linkType;
argument._Int = linkIndex;
switch (argumentType)
{
case PersistentArgumentType.Bool:
case PersistentArgumentType.String:
case PersistentArgumentType.Int:
case PersistentArgumentType.Float:
case PersistentArgumentType.Vector2:
case PersistentArgumentType.Vector3:
case PersistentArgumentType.Vector4:
case PersistentArgumentType.Quaternion:
case PersistentArgumentType.Color:
case PersistentArgumentType.Color32:
case PersistentArgumentType.Rect:
argument._X = (float)argumentType;
break;
case PersistentArgumentType.Enum:
case PersistentArgumentType.Object:
argument._String = DrawerState.Current.CurrentParameter.ParameterType.AssemblyQualifiedName;
break;
case PersistentArgumentType.Parameter:
case PersistentArgumentType.ReturnValue:
throw new InvalidOperationException(Names.PersistentArgument.Class + " was already linked.");
default:
throw new InvalidOperationException("Invalid " + Names.PersistentArgument.Full.Type + ": " + argumentType);
}
}
}, wasLink ? "Unlink Argument" : "Link Argument");
}
}
/************************************************************************************************************************/
}
}
#endif

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2b2c2ed6b237b7f40b961d3cd4be88ce
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,600 @@
// UltEvents // Copyright 2021 Kybernetik //
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
using Object = UnityEngine.Object;
namespace UltEvents.Editor
{
[CustomPropertyDrawer(typeof(PersistentCall), true)]
internal sealed class PersistentCallDrawer : PropertyDrawer
{
/************************************************************************************************************************/
public const float
RowHeight = 16;
/************************************************************************************************************************/
public const float
Padding = 2;
/************************************************************************************************************************/
public const float
SuggestionButtonWidth = 16;
public static readonly GUIStyle
PopupButtonStyle;
public static readonly GUIStyle
PopupLabelStyle;
private static readonly GUIContent
ArgumentLabel = new GUIContent();
private static readonly GUIContent
MethodNameSuggestionLabel = new GUIContent("?", "Suggest a method name");
public static readonly Color
ErrorFieldColor = new Color(1, 0.65f, 0.65f);
/************************************************************************************************************************/
static PersistentCallDrawer()
{
PopupButtonStyle = new GUIStyle(EditorStyles.popup)
{
fixedHeight = RowHeight
};
PopupLabelStyle = new GUIStyle(GUI.skin.label)
{
fontSize = 10,
alignment = TextAnchor.MiddleLeft,
padding = new RectOffset(4, 14, 0, 0)
};
}
/************************************************************************************************************************/
public override float GetPropertyHeight(SerializedProperty callProperty, GUIContent label)
{
if (callProperty.hasMultipleDifferentValues)
{
if (DrawerState.GetPersistentArgumentsProperty(callProperty).hasMultipleDifferentValues)
return EditorGUIUtility.singleLineHeight;
if (DrawerState.GetMethodNameProperty(callProperty).hasMultipleDifferentValues)
return EditorGUIUtility.singleLineHeight;
}
if (DrawerState.GetCall(callProperty).GetMethodSafe() == null)
return EditorGUIUtility.singleLineHeight;
callProperty = DrawerState.GetPersistentArgumentsProperty(callProperty);
return (EditorGUIUtility.singleLineHeight + Padding) * (1 + callProperty.arraySize) - Padding;
}
/************************************************************************************************************************/
public override void OnGUI(Rect area, SerializedProperty callProperty, GUIContent label)
{
DrawerState.Current.BeginCall(callProperty);
var propertyarea = area;
// If we are in the reorderable list of an event, adjust the property area to cover the list bounds.
if (DrawerState.Current.CachePreviousCalls)
{
propertyarea.xMin -= 20;
propertyarea.yMin -= 4;
propertyarea.width += 4;
}
label = EditorGUI.BeginProperty(propertyarea, label, callProperty);
{
const float Space = 2;
var x = area.x;
var xMax = area.xMax;
area.height = RowHeight;
// Target Field.
area.xMax = EditorGUIUtility.labelWidth + 12;
bool autoOpenMethodMenu;
DoTargetFieldGUI(area,
DrawerState.Current.TargetProperty, DrawerState.Current.MethodNameProperty,
out autoOpenMethodMenu);
EditorGUI.showMixedValue = DrawerState.Current.PersistentArgumentsProperty.hasMultipleDifferentValues || DrawerState.Current.MethodNameProperty.hasMultipleDifferentValues;
var method = EditorGUI.showMixedValue ? null : DrawerState.Current.call.GetMethodSafe();
// Method Name Dropdown.
area.x += area.width + Space;
area.xMax = xMax;
DoMethodFieldGUI(area, method, autoOpenMethodMenu);
// Persistent Arguments.
if (method != null)
{
area.x = x;
area.xMax = xMax;
DrawerState.Current.callParameters = method.GetParameters();
if (DrawerState.Current.callParameters.Length == DrawerState.Current.PersistentArgumentsProperty.arraySize)
{
var labelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth -= area.x - 14;
for (int i = 0; i < DrawerState.Current.callParameters.Length; i++)
{
DrawerState.Current.parameterIndex = i;
area.y += area.height + Padding;
ArgumentLabel.text = DrawerState.Current.callParameters[i].Name;
var argumentProperty = DrawerState.Current.PersistentArgumentsProperty.GetArrayElementAtIndex(i);
if (argumentProperty.propertyPath != "")
{
EditorGUI.PropertyField(area, argumentProperty, ArgumentLabel);
}
else
{
if (GUI.Button(area, new GUIContent(
"Reselect these objects to show arguments",
"This is the result of a bug in the way Unity updates the SerializedProperty for an array after it is resized while multiple objects are selected")))
{
var selection = Selection.objects;
Selection.objects = new Object[0];
EditorApplication.delayCall += () => Selection.objects = selection;
}
break;
}
}
EditorGUIUtility.labelWidth = labelWidth;
}
else
{
Debug.LogError("Method parameter count doesn't match serialized argument count " + DrawerState.Current.callParameters.Length
+ " : " + DrawerState.Current.PersistentArgumentsProperty.arraySize);
}
DrawerState.Current.callParameters = null;
}
EditorGUI.showMixedValue = false;
}
EditorGUI.EndProperty();
DrawerState.Current.EndCall();
}
/************************************************************************************************************************/
#region Target Field
/************************************************************************************************************************/
private static void DoTargetFieldGUI(Rect area, SerializedProperty targetProperty, SerializedProperty methodNameProperty, out bool autoOpenMethodMenu)
{
autoOpenMethodMenu = false;
// Type field for a static type.
if (targetProperty.objectReferenceValue == null && !targetProperty.hasMultipleDifferentValues)
{
var methodName = methodNameProperty.stringValue;
string typeName;
var lastDot = methodName.LastIndexOf('.');
if (lastDot >= 0)
{
typeName = methodName.Substring(0, lastDot);
lastDot++;
methodName = methodName.Substring(lastDot, methodName.Length - lastDot);
}
else typeName = "";
var color = GUI.color;
if (Type.GetType(typeName) == null)
GUI.color = ErrorFieldColor;
const float
ObjectPickerButtonWidth = 35,
Padding = 2;
area.width -= ObjectPickerButtonWidth + Padding;
EditorGUI.BeginChangeCheck();
typeName = ObjectPicker.DrawTypeField(area, typeName, GetAllTypes, 0, EditorStyles.miniButton);
if (EditorGUI.EndChangeCheck())
{
methodNameProperty.stringValue = typeName + "." + methodName;
}
HandleTargetFieldDragAndDrop(area, ref autoOpenMethodMenu);
GUI.color = color;
area.x += area.width + Padding;
area.width = ObjectPickerButtonWidth;
}
// Object field for an object reference.
DoTargetObjectFieldGUI(area, targetProperty, ref autoOpenMethodMenu);
}
/************************************************************************************************************************/
private static void DoTargetObjectFieldGUI(Rect area, SerializedProperty targetProperty, ref bool autoOpenMethodMenu)
{
if (targetProperty.hasMultipleDifferentValues)
EditorGUI.showMixedValue = true;
EditorGUI.BeginChangeCheck();
var oldTarget = targetProperty.objectReferenceValue;
var target = EditorGUI.ObjectField(area, oldTarget, typeof(Object), true);
if (EditorGUI.EndChangeCheck())
{
SetBestTarget(oldTarget, target, out autoOpenMethodMenu);
}
EditorGUI.showMixedValue = false;
}
/************************************************************************************************************************/
private static List<Type> _AllTypes;
private static List<Type> GetAllTypes()
{
if (_AllTypes == null)
{
// Gather all types in all currently loaded assemblies.
_AllTypes = new List<Type>(4192);
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
for (int i = 0; i < assemblies.Length; i++)
{
var types = assemblies[i].GetTypes();
for (int j = 0; j < types.Length; j++)
{
var type = types[j];
if (!type.ContainsGenericParameters &&// No Generics (because the type picker field doesn't let you pick generic parameters).
!type.IsInterface &&// No Interfaces (because they can't have any static methods).
!type.IsDefined(typeof(ObsoleteAttribute), true) &&// No Obsoletes.
type.GetMethods(UltEventUtils.StaticBindings).Length > 0)// No types without any static methods.
{
// The type might still not have any valid methods, but at least we've narrowed down the list a lot.
_AllTypes.Add(type);
}
}
}
_AllTypes.Sort((a, b) => a.FullName.CompareTo(b.FullName));
// We probably just allocated thousands of arrays with all those GetMethods calls, so call for a cleanup imediately.
GC.Collect();
}
return _AllTypes;
}
/************************************************************************************************************************/
private static void HandleTargetFieldDragAndDrop(Rect area, ref bool autoOpenMethodMenu)
{
// Drag and drop objects into the type field.
switch (Event.current.type)
{
case EventType.Repaint:
case EventType.DragUpdated:
{
if (!area.Contains(Event.current.mousePosition))
break;
var dragging = DragAndDrop.objectReferences;
if (dragging != null && dragging.Length == 1)
{
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
}
}
break;
case EventType.DragPerform:
{
if (!area.Contains(Event.current.mousePosition))
break;
var dragging = DragAndDrop.objectReferences;
if (dragging != null && dragging.Length == 1)
{
SetBestTarget(DrawerState.Current.TargetProperty.objectReferenceValue, dragging[0], out autoOpenMethodMenu);
DragAndDrop.AcceptDrag();
GUI.changed = true;
}
}
break;
default:
break;
}
}
/************************************************************************************************************************/
private static void SetBestTarget(Object oldTarget, Object newTarget, out bool autoOpenMethodMenu)
{
// It's more likely that the user intends to target a method on a Component than the GameObject itself so
// if a GameObject was dropped in, try to select a component with the same type as the old target,
// otherwise select it's first component after the Transform.
var gameObject = newTarget as GameObject;
if (!(oldTarget is GameObject) && !ReferenceEquals(gameObject, null))
{
var oldComponent = oldTarget as Component;
if (!ReferenceEquals(oldComponent, null))
{
newTarget = gameObject.GetComponent(oldComponent.GetType());
if (newTarget != null)
goto FoundTarget;
}
var components = gameObject.GetComponents<Component>();
newTarget = components.Length > 1 ? components[1] : components[0];
}
FoundTarget:
SetTarget(newTarget);
autoOpenMethodMenu = BoolPref.AutoOpenMenu && newTarget != null && DrawerState.Current.call.GetMethodSafe() == null;
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
private static void DoMethodFieldGUI(Rect area, MethodBase method, bool autoOpenMethodMenu)
{
EditorGUI.BeginProperty(area, null, DrawerState.Current.MethodNameProperty);
{
if (includeRemoveButton)
area.width -= RemoveButtonWidth;
var color = GUI.color;
string label;
if (EditorGUI.showMixedValue)
{
label = "Mixed Values";
}
else if (method != null)
{
label = MethodSelectionMenu.GetMethodSignature(method, false);
DoGetSetToggleGUI(ref area, method);
}
else
{
var methodName = DrawerState.Current.MethodNameProperty.stringValue;
Type declaringType;
PersistentCall.GetMethodDetails(methodName,
DrawerState.Current.TargetProperty.objectReferenceValue,
out declaringType, out label);
DoMethodNameSuggestionGUI(ref area, declaringType, methodName);
GUI.color = ErrorFieldColor;
}
if (autoOpenMethodMenu || (GUI.Button(area, GUIContent.none, PopupButtonStyle) && Event.current.button == 0))
{
MethodSelectionMenu.ShowMenu(area);
}
GUI.color = color;
PopupLabelStyle.fontStyle = DrawerState.Current.MethodNameProperty.prefabOverride ? FontStyle.Bold : FontStyle.Normal;
GUI.Label(area, label, PopupLabelStyle);
}
EditorGUI.EndProperty();
}
/************************************************************************************************************************/
private static float _GetSetWidth;
private static float GetSetWidth
{
get
{
if (_GetSetWidth <= 0)
{
float _, width;
ArgumentLabel.text = "Get";
GUI.skin.button.CalcMinMaxWidth(ArgumentLabel, out _, out width);
ArgumentLabel.text = "Set";
GUI.skin.button.CalcMinMaxWidth(ArgumentLabel, out _, out _GetSetWidth);
if (_GetSetWidth < width)
_GetSetWidth = width;
}
return _GetSetWidth;
}
}
/************************************************************************************************************************/
private static void DoGetSetToggleGUI(ref Rect area, MethodBase method)
{
// Check if the method name starts with "get_" or "set_".
// Check the underscore first since it's hopefully the rarest so it can break out early.
var name = method.Name;
if (name.Length <= 4 || name[3] != '_' || name[2] != 't' || name[1] != 'e')
return;
var first = name[0];
var isGet = first == 'g';
var isSet = first == 's';
if (!isGet && !isSet)
return;
var methodName = (isGet ? "set_" : "get_") + name.Substring(4, name.Length - 4);
var oppositePropertyMethod = method.DeclaringType.GetMethod(methodName, UltEventUtils.AnyAccessBindings);
if (oppositePropertyMethod == null ||
(isGet && !MethodSelectionMenu.IsSupported(method.GetReturnType())))
return;
area.width -= GetSetWidth + Padding;
var buttonArea = new Rect(
area.x + area.width + Padding,
area.y,
GetSetWidth,
area.height);
if (GUI.Button(buttonArea, isGet ? "Get" : "Set"))
{
var cachedState = new DrawerState();
cachedState.CopyFrom(DrawerState.Current);
EditorApplication.delayCall += () =>
{
DrawerState.Current.CopyFrom(cachedState);
SetMethod(oppositePropertyMethod);
DrawerState.Current.Clear();
InternalEditorUtility.RepaintAllViews();
};
}
}
/************************************************************************************************************************/
private static void DoMethodNameSuggestionGUI(ref Rect area, Type declaringType, string methodName)
{
if (declaringType == null ||
string.IsNullOrEmpty(methodName))
return;
var lastDot = methodName.LastIndexOf('.');
if (lastDot >= 0)
{
lastDot++;
if (lastDot >= methodName.Length)
return;
methodName = methodName.Substring(lastDot);
}
var methods = declaringType.GetMethods(UltEventUtils.AnyAccessBindings);
if (methods.Length == 0)
return;
area.width -= SuggestionButtonWidth + Padding;
var buttonArea = new Rect(
area.x + area.width + Padding,
area.y,
SuggestionButtonWidth,
area.height);
if (GUI.Button(buttonArea, MethodNameSuggestionLabel))
{
var cachedState = new DrawerState();
cachedState.CopyFrom(DrawerState.Current);
EditorApplication.delayCall += () =>
{
DrawerState.Current.CopyFrom(cachedState);
var bestMethod = methods[0];
var bestDistance = UltEventUtils.CalculateLevenshteinDistance(methodName, bestMethod.Name);
var i = 1;
for (; i < methods.Length; i++)
{
var method = methods[i];
var distance = UltEventUtils.CalculateLevenshteinDistance(methodName, method.Name);
if (bestDistance > distance)
{
bestDistance = distance;
bestMethod = method;
}
}
SetMethod(bestMethod);
DrawerState.Current.Clear();
InternalEditorUtility.RepaintAllViews();
};
}
}
/************************************************************************************************************************/
public static void SetTarget(Object target)
{
DrawerState.Current.TargetProperty.objectReferenceValue = target;
DrawerState.Current.TargetProperty.serializedObject.ApplyModifiedProperties();
if (target == null ||
DrawerState.Current.call.GetMethodSafe() == null)
{
SetMethod(null);
}
}
/************************************************************************************************************************/
public static void SetMethod(MethodInfo methodInfo)
{
DrawerState.Current.CallProperty.ModifyValues<PersistentCall>((call) =>
{
if (call != null)
call.SetMethod(methodInfo, DrawerState.Current.TargetProperty.objectReferenceValue);
}, "Set Method");
}
/************************************************************************************************************************/
#region Remove Button
/************************************************************************************************************************/
public const float RemoveButtonWidth = 18;
public static bool includeRemoveButton;
/************************************************************************************************************************/
public static bool DoRemoveButtonGUI(Rect rowArea)
{
includeRemoveButton = false;
rowArea.xMin = rowArea.xMax - RemoveButtonWidth + 2;
rowArea.height = EditorGUIUtility.singleLineHeight + 2;
return GUI.Button(rowArea, ReorderableList.defaultBehaviours.iconToolbarMinus, ReorderableList.defaultBehaviours.preButton);
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
}
}
#endif

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e04da5e866e49944f9c15d85a6ef2c68
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2d916606b97e4cb4596e7a5a6c505f54
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,198 @@
// UltEvents // Copyright 2021 Kybernetik //
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace UltEvents.Editor
{
internal static class SerializedPropertyContextMenu
{
/************************************************************************************************************************/
[InitializeOnLoadMethod]
private static void OnPropertyContextMenu()
{
EditorApplication.contextualPropertyMenu += (menu, property) =>
{
if (property.propertyType != SerializedPropertyType.Generic)
return;
var accessor = property.GetAccessor();
if (accessor == null)
return;
var type = accessor.GetFieldElementType(property);
if (typeof(UltEventBase).IsAssignableFrom(type))
{
AddEventFunctions(menu, property, accessor);
BoolPref.AddDisplayOptions(menu);
}
else if (type == typeof(PersistentCall))
{
AddCallClipboardItems(menu, property, accessor);
BoolPref.AddDisplayOptions(menu);
}
};
}
/************************************************************************************************************************/
public static void AddEventFunctions(GenericMenu menu, SerializedProperty property,
Serialization.PropertyAccessor accessor)
{
property = property.Copy();
var type = accessor.GetFieldElementType(property);
if (type == typeof(UltEvent))
{
menu.AddItem(new GUIContent("Invoke Event"), false, () =>
{
var events = property.GetValues<UltEvent>();
for (int i = 0; i < events.Length; i++)
{
var e = events[i] as UltEvent;
if (e != null)
e.Invoke();
}
property.OnPropertyChanged();
});
}
AddEventClipboardItems(menu, property, accessor);
menu.AddItem(new GUIContent("Clear Event"), false, () =>
{
property.ModifyValues<UltEventBase>((e) =>
{
if (e != null)
e.Clear();
}, "Clear Event");
});
menu.AddItem(new GUIContent("Log Description"), false, () =>
{
var targets = property.serializedObject.targetObjects;
var events = property.GetValues<UltEventBase>();
for (int i = 0; i < events.Length; i++)
Debug.Log(events[i], targets[i]);
});
}
/************************************************************************************************************************/
private static void AddEventClipboardItems(GenericMenu menu, SerializedProperty property,
Serialization.PropertyAccessor accessor)
{
// Copy Event.
menu.AddItem(new GUIContent("Copy Event"), false, () =>
{
Clipboard.CopyEvent(property);
});
// Paste Event.
AddMenuItem(menu, "Paste Event (Overwrite)", Clipboard.HasEvent, () =>
{
property.ModifyValues<UltEventBase>((e) =>
{
Clipboard.Paste(e);
}, "Paste Event");
});
// Paste Listener.
AddMenuItem(menu, "Paste Listener (New) %#V", Clipboard.HasCall, () =>
{
property.ModifyValues<UltEventBase>((e) =>
{
var call = new PersistentCall();
Clipboard.PasteCall(call);
if (e._PersistentCalls == null)
e._PersistentCalls = new List<PersistentCall>();
e._PersistentCalls.Add(call);
}, "Paste PersistentCall");
});
}
/************************************************************************************************************************/
private static void AddCallClipboardItems(GenericMenu menu, SerializedProperty property,
Serialization.PropertyAccessor accessor)
{
menu.AddItem(new GUIContent("Copy Listener %C"), false, () =>
{
Clipboard.CopyCall(property);
});
AddMenuItem(menu, "Paste Listener (Overwrite) %V", Clipboard.HasCall, () => Clipboard.PasteCall(property));
}
/************************************************************************************************************************/
public static void AddMenuItem(GenericMenu menu, string label, bool enabled, GenericMenu.MenuFunction function)
{
if (enabled)
{
menu.AddItem(new GUIContent(label), false, function);
}
else
{
menu.AddDisabledItem(new GUIContent(label));
}
}
/************************************************************************************************************************/
public static void AddPropertyModifierFunction(GenericMenu menu, SerializedProperty property, string label, Action function)
{
menu.AddItem(new GUIContent(label), false, () =>
{
function();
property.serializedObject.ApplyModifiedProperties();
});
}
public static void AddPropertyModifierFunction(GenericMenu menu, SerializedProperty property, string label,
Action<SerializedProperty> function)
{
menu.AddItem(new GUIContent(label), false, () =>
{
ForEachTarget(property, function);
});
}
/************************************************************************************************************************/
private static void ForEachTarget(SerializedProperty property, Action<SerializedProperty> function)
{
var targets = property.serializedObject.targetObjects;
if (targets.Length == 1)
{
function(property);
property.serializedObject.ApplyModifiedProperties();
}
else
{
var path = property.propertyPath;
for (int i = 0; i < targets.Length; i++)
{
using (var serializedObject = new SerializedObject(targets[i]))
{
property = serializedObject.FindProperty(path);
function(property);
property.serializedObject.ApplyModifiedProperties();
}
}
}
}
/************************************************************************************************************************/
}
}
#endif

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 47e623a49203d0e4fa67df0c9f6db408
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,636 @@
// UltEvents // Copyright 2021 Kybernetik //
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
using Object = UnityEngine.Object;
namespace UltEvents.Editor
{
[CustomPropertyDrawer(typeof(UltEventBase), true)]
internal sealed class UltEventDrawer : PropertyDrawer
{
/************************************************************************************************************************/
public const float
Border = 1;
/************************************************************************************************************************/
public const float
Padding = 5;
/************************************************************************************************************************/
public const float
IndentSize = 15;
private static readonly GUIContent
EventLabel = new GUIContent();
private static readonly GUIContent
CountLabel = new GUIContent();
private static readonly GUIContent
PlusLabel = EditorGUIUtility.IconContent("Toolbar Plus", "Add to list");
private static readonly GUIStyle
HeaderBackground = new GUIStyle("RL Header");
private static readonly GUIStyle
PlusButton = "RL FooterButton";
private static ReorderableList _CurrentCallList;
private static int _CurrentCallCount;
/************************************************************************************************************************/
static UltEventDrawer()
{
HeaderBackground.fixedHeight -= 1;
}
/************************************************************************************************************************/
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
if (!property.isExpanded)
{
return EditorGUIUtility.singleLineHeight;
}
else
{
if (!DrawerState.Current.TryBeginEvent(property))
return EditorGUIUtility.singleLineHeight;
CachePersistentCallList(property);
DrawerState.Current.EndEvent();
return _CurrentCallList.GetHeight() - 1;
}
}
/************************************************************************************************************************/
private float CalculateCallHeight(int index)
{
if (index >= 0 && index < _CurrentCallCount)
{
var height = EditorGUI.GetPropertyHeight(_CurrentCallList.serializedProperty.GetArrayElementAtIndex(index));
height += Border * 2 + Padding;
if (index == _CurrentCallCount - 1)
height -= Padding - 1;
return height;
}
else return 0;
}
/************************************************************************************************************************/
public override void OnGUI(Rect area, SerializedProperty property, GUIContent label)
{
if (!DrawerState.Current.TryBeginEvent(property))
return;
EventLabel.text = label.text + DrawerState.Current.Event.ParameterString;
EventLabel.tooltip = label.tooltip;
if (BoolPref.UseIndentation)
area = EditorGUI.IndentedRect(area);
area.y -= 1;
var indentLevel = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
CachePersistentCallList(property);
if (property.isExpanded)
{
DrawerState.Current.BeginCache();
_CurrentCallList.DoList(area);
DrawerState.Current.EndCache();
}
else
{
if (Event.current.type == EventType.Repaint)
HeaderBackground.Draw(area, false, false, false, false);
DoHeaderGUI(new Rect(area.x + 6, area.y + 1, area.width - 12, area.height));
}
area.y += 1;
area.height = HeaderBackground.fixedHeight;
property.isExpanded = EditorGUI.Foldout(area, property.isExpanded, "", true);
CheckDragDrop(area);
EditorGUI.indentLevel = indentLevel;
DrawerState.Current.EndEvent();
}
/************************************************************************************************************************/
private readonly Dictionary<string, ReorderableList>
PropertyPathToList = new Dictionary<string, ReorderableList>();
private void CachePersistentCallList(SerializedProperty eventProperty)
{
var path = eventProperty.propertyPath;
if (!PropertyPathToList.TryGetValue(path, out _CurrentCallList))
{
eventProperty = eventProperty.FindPropertyRelative(Names.UltEvent.PersistentCalls);
_CurrentCallList = new ReorderableList(eventProperty.serializedObject, eventProperty, true, true, true, true)
{
drawHeaderCallback = DoHeaderGUI,
drawElementCallback = DoPersistentCallGUI,
drawFooterCallback = DoFooterGUI,
onAddCallback = AddNewCall,
onReorderCallback = OnReorder,
elementHeight = 19,// Used when the list is empty.
elementHeightCallback = CalculateCallHeight,
drawElementBackgroundCallback = DoElementBackgroundGUI,
#if UNITY_2018_1_OR_NEWER
drawNoneElementCallback = DoNoneElementGUI,
#endif
};
PropertyPathToList.Add(path, _CurrentCallList);
}
_CurrentCallCount = _CurrentCallList.count;
RecalculateFooter();
}
/************************************************************************************************************************/
private static float _DefaultFooterHeight;
private void RecalculateFooter()
{
if (_DefaultFooterHeight == 0)
_DefaultFooterHeight = _CurrentCallList.footerHeight;
if (BoolPref.AutoHideFooter && !DrawerState.Current.Event.HasAnyDynamicCalls())
{
_CurrentCallList.footerHeight = 0;
}
else
{
_CurrentCallList.footerHeight = _DefaultFooterHeight;
if (DrawerState.Current.EventProperty.isExpanded &&
DrawerState.Current.EventProperty.serializedObject.targetObjects.Length == 1)
{
if (DrawerState.Current.Event.HasAnyDynamicCalls())
_CurrentCallList.footerHeight +=
DrawerState.Current.Event.GetDynamicCallInvocationListCount() * EditorGUIUtility.singleLineHeight + 1;
}
}
}
/************************************************************************************************************************/
private void DoHeaderGUI(Rect area)
{
EditorGUI.BeginProperty(area, GUIContent.none, DrawerState.Current.EventProperty);
const float
AddButtonWidth = 16,
AddButtonPadding = 2;
var labelStyle = DrawerState.Current.EventProperty.prefabOverride ? EditorStyles.boldLabel : GUI.skin.label;
CountLabel.text = _CurrentCallCount.ToString();
var countLabelWidth = labelStyle.CalcSize(CountLabel).x;
area.width -= AddButtonWidth + AddButtonPadding + countLabelWidth;
GUI.Label(area, EventLabel, labelStyle);
area.x += area.width;
area.width = countLabelWidth;
GUI.Label(area, CountLabel, labelStyle);
area.x += area.width + AddButtonPadding + 1;
area.width = AddButtonWidth;
#if UNITY_2019_3_OR_NEWER
area.y += 1;
#else
area.y -= 1;
#endif
if (GUI.Button(area, PlusLabel, PlusButton))
{
DrawerState.Current.EventProperty.isExpanded = true;
AddNewCall(_CurrentCallList);
}
EditorGUI.EndProperty();
}
/************************************************************************************************************************/
public void DoElementBackgroundGUI(Rect area, int index, bool selected, bool focused)
{
if (Event.current.type != EventType.Repaint)
return;
area.y -= 2;
area.height = CalculateCallHeight(index) + 2;
if (index == _CurrentCallCount - 1)
area.height += 2;
ReorderableList.defaultBehaviours.elementBackground.Draw(area, false, selected, selected, focused);
if (index >= 0 && index < _CurrentCallCount - 1)
{
area.xMin += 1;
area.xMax -= 1;
area.y += area.height - 3;
area.height = 1;
DoSeparatorLineGUI(area);
}
}
/************************************************************************************************************************/
private void DoNoneElementGUI(Rect area)
{
EditorGUI.BeginProperty(area, GUIContent.none, DrawerState.Current.EventProperty);
if (GUI.Button(area, "Click to add a listener", GUI.skin.label) &&
Event.current.button == 0)
{
AddNewCall(_CurrentCallList);
}
EditorGUI.EndProperty();
}
/************************************************************************************************************************/
private static GUIStyle _SeparatorLineStyle;
private static readonly Color SeparatorLineColor =
EditorGUIUtility.isProSkin ? new Color(0.157f, 0.157f, 0.157f) : new Color(0.5f, 0.5f, 0.5f);
private static void DoSeparatorLineGUI(Rect area)
{
if (Event.current.type == EventType.Repaint)
{
if (_SeparatorLineStyle == null)
{
_SeparatorLineStyle = new GUIStyle();
_SeparatorLineStyle.normal.background = EditorGUIUtility.whiteTexture;
_SeparatorLineStyle.stretchWidth = true;
}
var oldColor = GUI.color;
GUI.color = SeparatorLineColor;
_SeparatorLineStyle.Draw(area, false, false, false, false);
GUI.color = oldColor;
}
}
/************************************************************************************************************************/
private void DoPersistentCallGUI(Rect area, int index, bool isActive, bool isFocused)
{
DrawerState.Current.callIndex = index;
var callProperty = _CurrentCallList.serializedProperty.GetArrayElementAtIndex(index);
area.x += Border;
area.y += Border;
area.height -= Border * 2;
PersistentCallDrawer.includeRemoveButton = true;
EditorGUI.PropertyField(area, callProperty);
if (PersistentCallDrawer.DoRemoveButtonGUI(area))
DelayedRemoveCall(index);
if (isFocused)
CheckInput(index);
DrawerState.Current.callIndex = -1;
}
/************************************************************************************************************************/
private static GUIStyle _FooterBackground;
public void DoFooterGUI(Rect area)
{
if (area.height == 0)
return;
const float
InvokePadding = 2,
AddRemoveWidth = 16,
RightSideOffset = 5;
var width = area.width;
area.xMin -= 1;
// Background.
if (Event.current.type == EventType.Repaint)
{
if (_FooterBackground == null)
{
_FooterBackground = new GUIStyle(ReorderableList.defaultBehaviours.footerBackground)
{
fixedHeight = 0
};
}
_FooterBackground.Draw(area, false, false, false, false);
}
area.y -= 3;
area.width -= InvokePadding + AddRemoveWidth * 2 + RightSideOffset;
area.height = EditorGUIUtility.singleLineHeight;
if (DrawerState.Current.EventProperty.serializedObject.targetObjects.Length > 1)
{
// Multiple Objects Selected.
area.xMin += 2;
GUI.Label(area, "Can't show Dynamic Listeners for multiple objects");
}
else if (DrawerState.Current.Event != null)
{
area.xMin += 16;
var labelWidth = area.width;
area.xMax = EditorGUIUtility.labelWidth + IndentSize;
GUI.Label(area, "Dynamic Listeners");
// Dynamic Listener Foldout.
var dynamicListenerCount = DrawerState.Current.Event.GetDynamicCallInvocationListCount();
if (dynamicListenerCount > 0)
{
var isExpanded = EditorGUI.Foldout(area, _CurrentCallList.serializedProperty.isExpanded, GUIContent.none, true);
_CurrentCallList.serializedProperty.isExpanded = isExpanded;
if (isExpanded && DrawerState.Current.Event.HasAnyDynamicCalls())
{
DoDynamicListenerGUI(area.x, area.y + EditorGUIUtility.singleLineHeight - 1, width, DrawerState.Current.Event);
}
}
// Dynamic Listener Count.
area.x += area.width;
area.width = labelWidth - area.width;
GUI.Label(area, dynamicListenerCount.ToString());
}
// Add.
area.x += area.width + InvokePadding;
area.y -= 1;
area.width = AddRemoveWidth;
area.height = _DefaultFooterHeight;
if (GUI.Button(area, ReorderableList.defaultBehaviours.iconToolbarPlus, ReorderableList.defaultBehaviours.preButton))
{
AddNewCall(_CurrentCallList);
}
// Remove.
area.x += area.width;
using (new EditorGUI.DisabledScope(_CurrentCallList.index < 0 || _CurrentCallList.index >= _CurrentCallCount))
{
if (GUI.Button(area, ReorderableList.defaultBehaviours.iconToolbarMinus, ReorderableList.defaultBehaviours.preButton))
{
DelayedRemoveCall(_CurrentCallList.index);
}
}
}
/************************************************************************************************************************/
private void DoDynamicListenerGUI(float x, float y, float width, UltEventBase targetEvent)
{
x += IndentSize;
width -= IndentSize * 2;
var area = new Rect(x, y, width, EditorGUIUtility.singleLineHeight);
var calls = targetEvent.GetDynamicCallInvocationList();
for (int i = 0; i < calls.Length; i++)
{
var call = calls[i];
DoDelegateGUI(area, call);
area.y += area.height;
}
}
/************************************************************************************************************************/
/// <summary>[Editor-Only]
/// Draw the target and name of the specified <see cref="Delegate"/>.
/// </summary>
public static void DoDelegateGUI(Rect area, Delegate del)
{
var width = area.width;
area.xMax = EditorGUIUtility.labelWidth + 15;
var obj = del.Target as Object;
if (!ReferenceEquals(obj, null))
{
// If the target is a Unity Object, draw it in an Object Field so the user can click to ping the object.
using (new EditorGUI.DisabledScope(true))
{
EditorGUI.ObjectField(area, obj, typeof(Object), true);
}
}
else if (del.Method.DeclaringType.IsDefined(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), true))
{
// Anonymous Methods draw only their method name.
area.width = width;
GUI.Label(area, del.Method.GetNameCS());
return;
}
else if (del.Target == null)
{
GUI.Label(area, del.Method.DeclaringType.GetNameCS());
}
else
{
GUI.Label(area, del.Target.ToString());
}
area.x += area.width;
area.width = width - area.width;
GUI.Label(area, del.Method.GetNameCS(false));
}
/************************************************************************************************************************/
private void AddNewCall(ReorderableList list)
{
AddNewCall(list, list.serializedProperty.serializedObject.targetObject);
}
private void AddNewCall(ReorderableList list, Object target)
{
var index = list.index;
if (index >= 0 && index < _CurrentCallCount)
{
index++;
list.index = index;
}
else
{
index = _CurrentCallCount;
}
list.serializedProperty.InsertArrayElementAtIndex(index);
list.serializedProperty.serializedObject.ApplyModifiedProperties();
var callProperty = list.serializedProperty.GetArrayElementAtIndex(index);
DrawerState.Current.BeginCall(callProperty);
PersistentCallDrawer.SetTarget(target);
DrawerState.Current.EndCall();
}
/************************************************************************************************************************/
private static void RemoveCall(ReorderableList list, int index)
{
var property = list.serializedProperty;
property.DeleteArrayElementAtIndex(index);
if (list.index >= property.arraySize - 1)
list.index = property.arraySize - 1;
property.serializedObject.ApplyModifiedProperties();
}
private void DelayedRemoveCall(int index)
{
var list = _CurrentCallList;
var state = new DrawerState();
state.CopyFrom(DrawerState.Current);
EditorApplication.delayCall += () =>
{
DrawerState.Current.CopyFrom(state);
RemoveCall(list, index);
DrawerState.Current.UpdateLinkedArguments();
DrawerState.Current.Clear();
InternalEditorUtility.RepaintAllViews();
};
}
/************************************************************************************************************************/
private void OnReorder(ReorderableList list)
{
DrawerState.Current.UpdateLinkedArguments();
}
/************************************************************************************************************************/
private void CheckInput(int index)
{
var currentEvent = Event.current;
if (currentEvent.type == EventType.KeyUp)
{
switch (currentEvent.keyCode)
{
case KeyCode.Backspace:
case KeyCode.Delete:
RemoveCall(_CurrentCallList, index);
currentEvent.Use();
break;
case KeyCode.Plus:
case KeyCode.KeypadPlus:
case KeyCode.Equals:
AddNewCall(_CurrentCallList);
currentEvent.Use();
break;
case KeyCode.C:
if (currentEvent.control)
{
var property = _CurrentCallList.serializedProperty.GetArrayElementAtIndex(index);
Clipboard.CopyCall(property);
currentEvent.Use();
}
break;
case KeyCode.V:
if (currentEvent.control)
{
var property = _CurrentCallList.serializedProperty;
if (currentEvent.shift)
{
index++;
property.InsertArrayElementAtIndex(index);
property.serializedObject.ApplyModifiedProperties();
property = property.GetArrayElementAtIndex(index);
Clipboard.PasteCall(property);
}
else
{
property = property.GetArrayElementAtIndex(index);
Clipboard.PasteCall(property);
}
currentEvent.Use();
}
break;
}
}
}
/************************************************************************************************************************/
private void CheckDragDrop(Rect area)
{
if (!area.Contains(Event.current.mousePosition) ||
DragAndDrop.objectReferences.Length == 0)
return;
switch (Event.current.type)
{
case EventType.Repaint:
case EventType.DragUpdated:
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
break;
case EventType.DragPerform:
foreach (var drop in DragAndDrop.objectReferences)
{
AddNewCall(_CurrentCallList, drop);
}
DrawerState.Current.EventProperty.isExpanded = true;
DragAndDrop.AcceptDrag();
GUI.changed = true;
break;
default:
break;
}
}
/************************************************************************************************************************/
}
}
#endif

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 73265d0725df35a4089138d5c7068b2d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,3 @@
{
"name": "UltEvents"
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 57a6d82589ae03c4cabdc058f719f231
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a58292df9558ed736ce8b3bd7e2c8c1f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,44 @@
// UltEvents // Copyright 2021 Kybernetik //
using System;
using UnityEngine;
namespace UltEvents
{
/// <summary>
/// Stores arrays of various sizes so they can be reused without garbage collection.
/// </summary>
public static class ArrayCache<T>
{
/************************************************************************************************************************/
[ThreadStatic]
private static T[][] _Arrays;
/************************************************************************************************************************/
/// <summary>
/// Get a cached array of the specified size for temporary use. The array must be used and discarded
/// immediately as it may be reused by anything else that calls this method with the same `length`.
/// </summary>
public static T[] GetTempArray(int length)
{
if (_Arrays == null || _Arrays.Length <= length + 1)
{
var newSize = length < 16 ? 16 : Mathf.NextPowerOfTwo(length + 1);
Array.Resize(ref _Arrays, newSize);
}
var array = _Arrays[length];
if (array == null)
{
array = new T[length];
_Arrays[length] = array;
}
return array;
}
/************************************************************************************************************************/
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d7ec91f3e7c6b044798306a3f16441f6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,99 @@
// UltEvents // Copyright 2021 Kybernetik //
using UnityEngine;
namespace UltEvents
{
/// <summary>
/// An event that takes a single <see cref="Collision2D"/> parameter.
/// </summary>
[System.Serializable]
public sealed class CollisionEvent2D : UltEvent<Collision2D> { }
/************************************************************************************************************************/
/// <summary>
/// Holds <see cref="UltEvent"/>s which are called by various <see cref="MonoBehaviour"/> 2D collision events:
/// <see cref="OnCollisionEnter2D"/>, <see cref="OnCollisionStay2D"/>, and <see cref="OnCollisionExit2D"/>.
/// </summary>
[AddComponentMenu(UltEventUtils.ComponentMenuPrefix + "Collision Events 2D")]
[HelpURL(UltEventUtils.APIDocumentationURL + "/CollisionEvents2D")]
[DisallowMultipleComponent]
[RequireComponent(typeof(Collider2D))]
public class CollisionEvents2D : MonoBehaviour
{
/************************************************************************************************************************/
[SerializeField]
private CollisionEvent2D _CollisionEnterEvent;
/// <summary>Invoked by <see cref="OnCollisionEnter2D"/>.</summary>
public CollisionEvent2D CollisionEnterEvent
{
get
{
if (_CollisionEnterEvent == null)
_CollisionEnterEvent = new CollisionEvent2D();
return _CollisionEnterEvent;
}
set { _CollisionEnterEvent = value; }
}
/// <summary>Invokes <see cref="CollisionEnterEvent"/>.</summary>
public virtual void OnCollisionEnter2D(Collision2D collision)
{
if (_CollisionEnterEvent != null)
_CollisionEnterEvent.Invoke(collision);
}
/************************************************************************************************************************/
[SerializeField]
private CollisionEvent2D _CollisionStayEvent;
/// <summary>Invoked by <see cref="OnCollisionStay2D"/>.</summary>
public CollisionEvent2D CollisionStayEvent
{
get
{
if (_CollisionStayEvent == null)
_CollisionStayEvent = new CollisionEvent2D();
return _CollisionStayEvent;
}
set { _CollisionStayEvent = value; }
}
/// <summary>Invokes <see cref="CollisionStayEvent"/>.</summary>
public virtual void OnCollisionStay2D(Collision2D collision)
{
if (_CollisionStayEvent != null)
_CollisionStayEvent.Invoke(collision);
}
/************************************************************************************************************************/
[SerializeField]
private CollisionEvent2D _CollisionExitEvent;
/// <summary>Invoked by <see cref="OnCollisionExit2D"/>.</summary>
public CollisionEvent2D CollisionExitEvent
{
get
{
if (_CollisionExitEvent == null)
_CollisionExitEvent = new CollisionEvent2D();
return _CollisionExitEvent;
}
set { _CollisionExitEvent = value; }
}
/// <summary>Invokes <see cref="CollisionExitEvent"/>.</summary>
public virtual void OnCollisionExit2D(Collision2D collision)
{
if (_CollisionExitEvent != null)
_CollisionExitEvent.Invoke(collision);
}
/************************************************************************************************************************/
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3d9d795461ac3c041bf1635804658d32
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,99 @@
// UltEvents // Copyright 2021 Kybernetik //
using UnityEngine;
namespace UltEvents
{
/// <summary>
/// An event that takes a single <see cref="Collision"/> parameter.
/// </summary>
[System.Serializable]
public sealed class CollisionEvent3D : UltEvent<Collision> { }
/************************************************************************************************************************/
/// <summary>
/// Holds <see cref="UltEvent"/>s which are called by various <see cref="MonoBehaviour"/> collision events:
/// <see cref="OnCollisionEnter"/>, <see cref="OnCollisionStay"/>, and <see cref="OnCollisionExit"/>.
/// </summary>
[AddComponentMenu(UltEventUtils.ComponentMenuPrefix + "Collision Events 3D")]
[HelpURL(UltEventUtils.APIDocumentationURL + "/CollisionEvents3D")]
[DisallowMultipleComponent]
[RequireComponent(typeof(Collider))]
public class CollisionEvents3D : MonoBehaviour
{
/************************************************************************************************************************/
[SerializeField]
private CollisionEvent3D _CollisionEnterEvent;
/// <summary>Invoked by <see cref="OnCollisionEnter"/>.</summary>
public CollisionEvent3D CollisionEnterEvent
{
get
{
if (_CollisionEnterEvent == null)
_CollisionEnterEvent = new CollisionEvent3D();
return _CollisionEnterEvent;
}
set { _CollisionEnterEvent = value; }
}
/// <summary>Invokes <see cref="CollisionEnterEvent"/>.</summary>
public virtual void OnCollisionEnter(Collision collision)
{
if (_CollisionEnterEvent != null)
_CollisionEnterEvent.Invoke(collision);
}
/************************************************************************************************************************/
[SerializeField]
private CollisionEvent3D _CollisionStayEvent;
/// <summary>Invoked by <see cref="OnCollisionStay"/>.</summary>
public CollisionEvent3D CollisionStayEvent
{
get
{
if (_CollisionStayEvent == null)
_CollisionStayEvent = new CollisionEvent3D();
return _CollisionStayEvent;
}
set { _CollisionStayEvent = value; }
}
/// <summary>Invokes <see cref="CollisionStayEvent"/>.</summary>
public virtual void OnCollisionStay(Collision collision)
{
if (_CollisionStayEvent != null)
_CollisionStayEvent.Invoke(collision);
}
/************************************************************************************************************************/
[SerializeField]
private CollisionEvent3D _CollisionExitEvent;
/// <summary>Invoked by <see cref="OnCollisionExit"/>.</summary>
public CollisionEvent3D CollisionExitEvent
{
get
{
if (_CollisionExitEvent == null)
_CollisionExitEvent = new CollisionEvent3D();
return _CollisionExitEvent;
}
set { _CollisionExitEvent = value; }
}
/// <summary>Invokes <see cref="CollisionExitEvent"/>.</summary>
public virtual void OnCollisionExit(Collision collision)
{
if (_CollisionExitEvent != null)
_CollisionExitEvent.Invoke(collision);
}
/************************************************************************************************************************/
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d8e0dc99dbc534d4e8bb37159f2d7429
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,60 @@
// UltEvents // Copyright 2021 Kybernetik //
using System.Collections;
using UnityEngine;
namespace UltEvents
{
/// <summary>
/// A component which encapsulates a single <see cref="UltEventBase"/> with a delay before its invocation.
/// </summary>
[AddComponentMenu(UltEventUtils.ComponentMenuPrefix + "Delayed Ult Event Holder")]
[HelpURL(UltEventUtils.APIDocumentationURL + "/DelayedUltEventHolder")]
public class DelayedUltEventHolder : UltEventHolder
{
/************************************************************************************************************************/
[SerializeField]
private float _Delay;
private WaitForSeconds _Wait;
/// <summary>
/// The number of seconds that will pass between calling <see cref="Invoke"/> and the event actually being invoked.
/// </summary>
public float Delay
{
get { return _Delay; }
set
{
_Delay = value;
_Wait = null;
}
}
/************************************************************************************************************************/
/// <summary>Waits for <see cref="Delay"/> seconds then calls Event.Invoke().</summary>
public override void Invoke()
{
if (_Delay < 0)
base.Invoke();
else
StartCoroutine(DelayedInvoke());
}
/************************************************************************************************************************/
private IEnumerator DelayedInvoke()
{
if (_Wait == null)
_Wait = new WaitForSeconds(_Delay);
yield return _Wait;
base.Invoke();
}
/************************************************************************************************************************/
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3f574d8b2153a0d4fa15f5de878f680c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,139 @@
// UltEvents // Copyright 2021 Kybernetik //
using UnityEngine;
namespace UltEvents
{
/// <summary>
/// Holds <see cref="UltEvent"/>s which are called by various <see cref="MonoBehaviour"/> lifecycle events:
/// <see cref="Awake"/>, <see cref="Start"/>, <see cref="OnEnable"/>, <see cref="OnDisable"/>, and
/// <see cref="OnDestroy"/>.
/// </summary>
[AddComponentMenu(UltEventUtils.ComponentMenuPrefix + "Life Cycle Events")]
[HelpURL(UltEventUtils.APIDocumentationURL + "/LifeCycleEvents")]
[DisallowMultipleComponent]
public class LifeCycleEvents : MonoBehaviour
{
/************************************************************************************************************************/
[SerializeField]
private UltEvent _AwakeEvent;
/// <summary>Invoked by <see cref="Awake"/>.</summary>
public UltEvent AwakeEvent
{
get
{
if (_AwakeEvent == null)
_AwakeEvent = new UltEvent();
return _AwakeEvent;
}
set { _AwakeEvent = value; }
}
/// <summary>Invokes <see cref="AwakeEvent"/>.</summary>
public virtual void Awake()
{
if (_AwakeEvent != null)
_AwakeEvent.Invoke();
}
/************************************************************************************************************************/
[SerializeField]
private UltEvent _StartEvent;
/// <summary>Invoked by <see cref="Start"/>.</summary>
public UltEvent StartEvent
{
get
{
if (_StartEvent == null)
_StartEvent = new UltEvent();
return _StartEvent;
}
set { _StartEvent = value; }
}
/// <summary>Invokes <see cref="StartEvent"/>.</summary>
public virtual void Start()
{
if (_StartEvent != null)
_StartEvent.Invoke();
}
/************************************************************************************************************************/
[SerializeField]
private UltEvent _EnableEvent;
/// <summary>Invoked by <see cref="OnEnable"/>.</summary>
public UltEvent EnableEvent
{
get
{
if (_EnableEvent == null)
_EnableEvent = new UltEvent();
return _EnableEvent;
}
set { _EnableEvent = value; }
}
/// <summary>Invokes <see cref="EnableEvent"/>.</summary>
public virtual void OnEnable()
{
if (_EnableEvent != null)
_EnableEvent.Invoke();
}
/************************************************************************************************************************/
[SerializeField]
private UltEvent _DisableEvent;
/// <summary>Invoked by <see cref="OnDisable"/>.</summary>
public UltEvent DisableEvent
{
get
{
if (_DisableEvent == null)
_DisableEvent = new UltEvent();
return _DisableEvent;
}
set { _DisableEvent = value; }
}
/// <summary>Invokes <see cref="DisableEvent"/>.</summary>
public virtual void OnDisable()
{
if (_DisableEvent != null)
_DisableEvent.Invoke();
}
/************************************************************************************************************************/
[SerializeField]
private UltEvent _DestroyEvent;
/// <summary>Invoked by <see cref="OnDestroy"/>.</summary>
public UltEvent DestroyEvent
{
get
{
if (_DestroyEvent == null)
_DestroyEvent = new UltEvent();
return _DestroyEvent;
}
set { _DestroyEvent = value; }
}
/// <summary>Invokes <see cref="DestroyEvent"/>.</summary>
public virtual void OnDestroy()
{
if (_DestroyEvent != null)
_DestroyEvent.Invoke();
}
/************************************************************************************************************************/
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 003c25630808c7243847bd9149749434
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,64 @@
// UltEvents // Copyright 2021 Kybernetik //
namespace UltEvents
{
/// <summary>[Editor-Only] The names of various types and members in <see cref="UltEvents"/>.</summary>
internal static class Names
{
public const string
Namespace = "UltEvents";
public const string
PersistentArgumentType = "PersistentArgumentType";
/// <summary>[Editor-Only] The names of various members in <see cref="UltEvents.PersistentArgument"/>.</summary>
internal static class PersistentArgument
{
public const string
Class = "PersistentArgument";
public const string
Type = "_Type";
public const string
Int = "_Int";
public const string
String = "_String";
public const string
X = "_X";
public const string
Y = "_Y";
public const string
Z = "_Z";
public const string
W = "_W";
public const string
Object = "_Object";
/// <summary>[Editor-Only] The full names of various members in <see cref="UltEvents.PersistentArgument"/>.</summary>
internal static class Full
{
public const string
Type = "PersistentArgument.Type";
}
}
/// <summary>[Editor-Only] The names of various members in <see cref="UltEvents.PersistentCall"/>.</summary>
internal static class PersistentCall
{
public const string
Target = "_Target";
public const string
MethodName = "_MethodName";
public const string
PersistentArguments = "_PersistentArguments";
}
/// <summary>[Editor-Only] The names of various members in <see cref="UltEvents.UltEvent"/>.</summary>
internal static class UltEvent
{
public const string
Class = "UltEvent";
public const string
PersistentCalls = "_PersistentCalls";
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2a322e55daf5c3e458a39587bd3ec3c5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,749 @@
// UltEvents // Copyright 2021 Kybernetik //
using System;
using UnityEngine;
using Object = UnityEngine.Object;
namespace UltEvents
{
/// <summary>The type identifier of a <see cref="PersistentArgument"/>.</summary>
public enum PersistentArgumentType
{
/// <summary>Type not set.</summary>
None,
// Uses _Int 0 or 1 as bool.
/// <summary><see cref="bool"/>.</summary>
Bool,
// Uses _String.
/// <summary><see cref="string"/>.</summary>
String,
// Uses _Int.
/// <summary><see cref="int"/>.</summary>
Int,
// Uses _Int for the value and _String for the assembly qualified name of the type.
/// <summary>Any kind of <see cref="System.Enum"/>.</summary>
Enum,
// Uses _X.
/// <summary><see cref="float"/>.</summary>
Float,
// Uses _X and _Y.
/// <summary><see cref="UnityEngine.Vector2"/>.</summary>
Vector2,
// Uses _X, _Y, and _Z.
/// <summary><see cref="UnityEngine.Vector3"/>.</summary>
Vector3,
// Uses _X, _Y, _Z, and _W.
/// <summary><see cref="UnityEngine.Vector4"/>.</summary>
Vector4,
// Uses _X, _Y, and _Z to store the euler angles.
/// <summary><see cref="UnityEngine.Quaternion"/>.</summary>
Quaternion,
// Uses _X, _Y, _Z, and _W as RGBA.
/// <summary><see cref="UnityEngine.Color"/>.</summary>
Color,
// Uses _Int to hold the RGBA bytes.
/// <summary><see cref="UnityEngine.Color32"/>.</summary>
Color32,
// Uses _X, _Y, _Z, and _W as X, Y, Width, Height.
/// <summary><see cref="UnityEngine.Rect"/>.</summary>
Rect,
// Uses _Object for the value and _String for the assembly qualified name of the type.
/// <summary><see cref="UnityEngine.Object"/>.</summary>
Object,
// Uses _Int for the index of the target parameter.
// If the type is a simple PersistentArgumentType (not Object or Enum), it is casted to a float and stored in _X.
// Otherwise the assembly qualified name of the type is stored in _String.
/// <summary>The value of a parameter passed to the event.</summary>
Parameter,
// Uses _Int for the index of the target call.
// If the type is a simple PersistentArgumentType (not Object or Enum), it is casted to a float and stored in _X.
// Otherwise the assembly qualified name of the type is stored in _String.
/// <summary>The return value by a previous <see cref="PersistentCall"/>.</summary>
ReturnValue,
}
/// <summary>
/// Encapsulates a variable so it can be serialized for <see cref="UltEventBase"/>.
/// </summary>
[Serializable]
public sealed class PersistentArgument
{
/************************************************************************************************************************/
#region Fields
/************************************************************************************************************************/
[SerializeField]
internal PersistentArgumentType _Type;
[SerializeField]
internal int _Int;
[SerializeField]
internal string _String;
[SerializeField]
internal float _X;
[SerializeField]
internal float _Y;
[SerializeField]
internal float _Z;
[SerializeField]
internal float _W;
[SerializeField]
internal Object _Object;
/************************************************************************************************************************/
[NonSerialized]
private Type _SystemType;
[NonSerialized]
internal bool _HasSystemType;
[NonSerialized]
private object _Value;
/************************************************************************************************************************/
/// <summary>Constructs a new <see cref="PersistentArgument"/> with default values.</summary>
public PersistentArgument() { }
/// <summary>Constructs a new <see cref="PersistentArgument"/> with the specified `type`.</summary>
public PersistentArgument(Type type)
{
_Type = GetArgumentType(type, out _String, out _Int);
_SystemType = type;
_HasSystemType = true;
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Properties
/************************************************************************************************************************/
/// <summary>The type identifier of this argument.</summary>
public PersistentArgumentType Type
{
get { return _Type; }
internal set
{
_Int = 0;
_X = _Y = _Z = _W = 0;
_String = "";
_Object = null;
_Type = value;
_HasSystemType = false;
_Value = null;
}
}
/************************************************************************************************************************/
/// <summary>
/// The <see cref="System.Type"/> of this argument.
/// </summary>
public Type SystemType
{
get
{
#if !UNITY_EDITOR// Ignore cache in the editor since it complicates the inspector GUI code.
if (!_HasSystemType)
#endif
{
_SystemType = GetArgumentType(_Type, _X, _String);
_HasSystemType = true;
}
return _SystemType;
}
internal set
{
// Can't pass _String and _Int in directly because setting the Type clears them.
string assemblyQualifiedName;
int linkIndex;
Type = GetArgumentType(value, out assemblyQualifiedName, out linkIndex);
_String = assemblyQualifiedName;
_Int = linkIndex;
_HasSystemType = true;
_SystemType = value;
_Value = null;
}
}
/************************************************************************************************************************/
/// <summary>The <see cref="bool"/> value of this argument.</summary>
public bool Bool
{
get
{
AssertType(PersistentArgumentType.Bool);
return _Int != 0;
}
set
{
AssertType(PersistentArgumentType.Bool);
_Int = value ? 1 : 0;
_Value = null;
}
}
/************************************************************************************************************************/
/// <summary>The <see cref="string"/> value of this argument.</summary>
public string String
{
get
{
AssertType(PersistentArgumentType.String);
return _String;
}
set
{
AssertType(PersistentArgumentType.String);
_String = value ?? "";
_Value = value;
}
}
/************************************************************************************************************************/
/// <summary>The <see cref="int"/> value of this argument.</summary>
public int Int
{
get
{
AssertType(PersistentArgumentType.Int);
return _Int;
}
set
{
AssertType(PersistentArgumentType.Int);
_Int = value;
_Value = null;
}
}
/************************************************************************************************************************/
/// <summary>The <see cref="System.Enum"/> value of this argument.</summary>
public object Enum
{
get
{
AssertType(PersistentArgumentType.Enum);
return System.Enum.ToObject(SystemType, _Int);
}
set
{
AssertType(PersistentArgumentType.Enum);
_Int = (int)value;
_Value = value;
}
}
/************************************************************************************************************************/
/// <summary>The <see cref="float"/> value of this argument.</summary>
public float Float
{
get
{
AssertType(PersistentArgumentType.Float);
return _X;
}
set
{
AssertType(PersistentArgumentType.Float);
_X = value;
_Value = null;
}
}
/// <summary>The <see cref="UnityEngine.Vector2"/> value of this argument.</summary>
public Vector2 Vector2
{
get
{
AssertType(PersistentArgumentType.Vector2);
return new Vector2(_X, _Y);
}
set
{
AssertType(PersistentArgumentType.Vector2);
_X = value.x;
_Y = value.y;
_Value = null;
}
}
/// <summary>The <see cref="UnityEngine.Vector3"/> value of this argument.</summary>
public Vector3 Vector3
{
get
{
AssertType(PersistentArgumentType.Vector3);
return new Vector3(_X, _Y, _Z);
}
set
{
AssertType(PersistentArgumentType.Vector3);
_X = value.x;
_Y = value.y;
_Z = value.z;
_Value = null;
}
}
/// <summary>The <see cref="UnityEngine.Vector4"/> value of this argument.</summary>
public Vector4 Vector4
{
get
{
AssertType(PersistentArgumentType.Vector4);
return new Vector4(_X, _Y, _Z, _W);
}
set
{
AssertType(PersistentArgumentType.Vector4);
_X = value.x;
_Y = value.y;
_Z = value.z;
_W = value.w;
_Value = null;
}
}
/************************************************************************************************************************/
/// <summary>The <see cref="UnityEngine.Quaternion"/> value of this argument.</summary>
public Quaternion Quaternion
{
// It would be better to store the components of the Quaternion directly instead of the euler angles,
// but floating point imprecision when converting between them to show the euler angles in the inspector
// means that changes to any one axis will have a small effect on the other axes as well.
// This could be handled like the [Euler] attribute, but that still leads to small inaccuracies in the
// displayed values when they are deserialized so storing the euler angles directly is more user-friendly.
get
{
AssertType(PersistentArgumentType.Quaternion);
return Quaternion.Euler(_X, _Y, _Z);
}
set
{
AssertType(PersistentArgumentType.Quaternion);
var euler = value.eulerAngles;
_X = euler.x;
_Y = euler.y;
_Z = euler.z;
_Value = null;
}
}
/************************************************************************************************************************/
/// <summary>The <see cref="UnityEngine.Color"/> value of this argument.</summary>
public Color Color
{
get
{
AssertType(PersistentArgumentType.Color);
return new Color(_X, _Y, _Z, _W);
}
set
{
AssertType(PersistentArgumentType.Color);
_X = value.r;
_Y = value.g;
_Z = value.b;
_W = value.a;
_Value = null;
}
}
/// <summary>The <see cref="UnityEngine.Color32"/> value of this argument.</summary>
public Color32 Color32
{
get
{
AssertType(PersistentArgumentType.Color32);
return new Color32((byte)(_Int), (byte)(_Int >> 8), (byte)(_Int >> 16), (byte)(_Int >> 24));
}
set
{
AssertType(PersistentArgumentType.Color32);
_Int = value.r | (value.g << 8) | (value.b << 16) | (value.a << 24);
_Value = null;
}
}
/************************************************************************************************************************/
/// <summary>The <see cref="UnityEngine.Rect"/> value of this argument.</summary>
public Rect Rect
{
get
{
AssertType(PersistentArgumentType.Rect);
return new Rect(_X, _Y, _Z, _W);
}
set
{
AssertType(PersistentArgumentType.Rect);
_X = value.x;
_Y = value.y;
_Z = value.width;
_W = value.height;
_Value = null;
}
}
/************************************************************************************************************************/
/// <summary>The <see cref="UnityEngine.Object"/> value of this argument.</summary>
public Object Object
{
get
{
AssertType(PersistentArgumentType.Object);
// Unity's fake nulls cause problems if the argument is a child type of UnityEngine.Object.
// For example, when invoking a MonoBehaviour parameter a fake null Object would fail to convert to MonoBehaviour.
// So we make sure to return actual null instead of any fake value.
if (_Object == null)
return null;
else
return _Object;
}
set
{
AssertType(PersistentArgumentType.Object);
_Object = value;
_String = value != null ? value.GetType().AssemblyQualifiedName : "";
_Value = value;
}
}
/************************************************************************************************************************/
/// <summary>The value of a parameter passed into the <see cref="PersistentCall"/> (see <see cref="ParameterIndex"/>.</summary>
public object Parameter
{
get
{
AssertType(PersistentArgumentType.Parameter);
return UltEventBase.GetParameterValue(_Int);
}
}
/// <summary>The index of the parameter passed into the <see cref="PersistentCall"/>.</summary>
public int ParameterIndex
{
get
{
AssertType(PersistentArgumentType.Parameter);
return _Int;
}
set
{
AssertType(PersistentArgumentType.Parameter);
_Int = value;
_Value = null;
}
}
/************************************************************************************************************************/
/// <summary>The value returned by a previous <see cref="PersistentCall"/> (see <see cref="ReturnedValueIndex"/>.</summary>
public object ReturnedValue
{
get
{
AssertType(PersistentArgumentType.ReturnValue);
return UltEventBase.GetReturnedValue(_Int);
}
}
/// <summary>The index of the <see cref="PersistentCall"/> which returns the value for this argument.</summary>
public int ReturnedValueIndex
{
get
{
AssertType(PersistentArgumentType.ReturnValue);
return _Int;
}
set
{
AssertType(PersistentArgumentType.ReturnValue);
_Int = value;
_Value = null;
}
}
/************************************************************************************************************************/
/// <summary>The value of this argument.</summary>
public object Value
{
get
{
if (_Value == null)
{
switch (_Type)
{
case PersistentArgumentType.Bool: _Value = Bool; break;
case PersistentArgumentType.String: _Value = String; break;
case PersistentArgumentType.Int: _Value = Int; break;
case PersistentArgumentType.Enum: _Value = Enum; break;
case PersistentArgumentType.Float: _Value = Float; break;
case PersistentArgumentType.Vector2: _Value = Vector2; break;
case PersistentArgumentType.Vector3: _Value = Vector3; break;
case PersistentArgumentType.Vector4: _Value = Vector4; break;
case PersistentArgumentType.Quaternion: _Value = Quaternion; break;
case PersistentArgumentType.Color: _Value = Color; break;
case PersistentArgumentType.Color32: _Value = Color32; break;
case PersistentArgumentType.Rect: _Value = Rect; break;
case PersistentArgumentType.Object: _Value = Object; break;
// Don't cache parameters or returned values.
case PersistentArgumentType.Parameter: return Parameter;
case PersistentArgumentType.ReturnValue: return ReturnedValue;
default:
throw new InvalidOperationException(
"Invalid " + Names.PersistentArgument.Full.Type + ": " + _Type);
}
}
return _Value;
}
set
{
switch (_Type)
{
case PersistentArgumentType.Bool: Bool = (bool)value; break;
case PersistentArgumentType.String: String = (string)value; break;
case PersistentArgumentType.Int: Int = (int)value; break;
case PersistentArgumentType.Enum: Enum = value; break;
case PersistentArgumentType.Float: Float = (float)value; break;
case PersistentArgumentType.Vector2: Vector2 = (Vector2)value; break;
case PersistentArgumentType.Vector3: Vector3 = (Vector3)value; break;
case PersistentArgumentType.Vector4: Vector4 = (Vector4)value; break;
case PersistentArgumentType.Quaternion: Quaternion = (Quaternion)value; break;
case PersistentArgumentType.Color: Color = (Color)value; break;
case PersistentArgumentType.Color32: Color32 = (Color32)value; break;
case PersistentArgumentType.Rect: Rect = (Rect)value; break;
case PersistentArgumentType.Object: Object = (Object)value; break;
// Don't cache parameters or returned values.
case PersistentArgumentType.Parameter: ParameterIndex = (int)value; return;
case PersistentArgumentType.ReturnValue: ReturnedValueIndex = (int)value; return;
default:
throw new InvalidOperationException(
"Invalid " + Names.PersistentArgument.Full.Type + ": " + _Type);
}
_Value = value;
}
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Methods
/************************************************************************************************************************/
[System.Diagnostics.Conditional("UNITY_EDITOR")]
private void AssertType(PersistentArgumentType type)
{
if (_Type != type)
throw new InvalidOperationException(Names.PersistentArgument.Full.Type + " is " + _Type + " but should be " + type);
}
/************************************************************************************************************************/
#if UNITY_EDITOR
internal void ClearCache()
{
_Value = null;
}
#endif
/************************************************************************************************************************/
/// <summary>
/// Returns the <see cref="System.Type"/> associated with the specified <see cref="PersistentArgumentType"/>.
/// <para></para>
/// If the `type` can be inherited (such as an Enum or Object), the `assemblyQualifiedName` will be used to get the type.
/// </summary>
public static Type GetArgumentType(PersistentArgumentType type, float secondaryType, string assemblyQualifiedName)
{
switch (type)
{
case PersistentArgumentType.Bool: return typeof(bool);
case PersistentArgumentType.String: return typeof(string);
case PersistentArgumentType.Int: return typeof(int);
case PersistentArgumentType.Float: return typeof(float);
case PersistentArgumentType.Vector2: return typeof(Vector2);
case PersistentArgumentType.Vector3: return typeof(Vector3);
case PersistentArgumentType.Vector4: return typeof(Vector4);
case PersistentArgumentType.Quaternion: return typeof(Quaternion);
case PersistentArgumentType.Color: return typeof(Color);
case PersistentArgumentType.Color32: return typeof(Color32);
case PersistentArgumentType.Rect: return typeof(Rect);
case PersistentArgumentType.Enum:
case PersistentArgumentType.Object:
default:
if (!string.IsNullOrEmpty(assemblyQualifiedName))
return System.Type.GetType(assemblyQualifiedName);
else
return null;
case PersistentArgumentType.Parameter:
case PersistentArgumentType.ReturnValue:
if (!string.IsNullOrEmpty(assemblyQualifiedName))
return System.Type.GetType(assemblyQualifiedName);
else
return GetArgumentType((PersistentArgumentType)secondaryType, -1, null);
case PersistentArgumentType.None:
return null;
}
}
/// <summary>
/// Returns the <see cref="PersistentArgumentType"/> associated with the specified <see cref="System.Type"/>.
/// <para></para>
/// If the `type` can be inherited (such as an Enum or Object), the `assemblyQualifiedName` will be assigned as well (otherwise null).
/// </summary>
public static PersistentArgumentType GetArgumentType(Type type, out string assemblyQualifiedName, out int linkIndex)
{
linkIndex = 0;
assemblyQualifiedName = null;
if (type == typeof(bool)) return PersistentArgumentType.Bool;
else if (type == typeof(string)) return PersistentArgumentType.String;
else if (type == typeof(int)) return PersistentArgumentType.Int;
else if (type == typeof(float)) return PersistentArgumentType.Float;
else if (type == typeof(Vector2)) return PersistentArgumentType.Vector2;
else if (type == typeof(Vector3)) return PersistentArgumentType.Vector3;
else if (type == typeof(Vector4)) return PersistentArgumentType.Vector4;
else if (type == typeof(Quaternion)) return PersistentArgumentType.Quaternion;
else if (type == typeof(Color)) return PersistentArgumentType.Color;
else if (type == typeof(Color32)) return PersistentArgumentType.Color32;
else if (type == typeof(Rect)) return PersistentArgumentType.Rect;
else if (type.IsEnum)
{
if (System.Enum.GetUnderlyingType(type) == typeof(int))
{
assemblyQualifiedName = type.AssemblyQualifiedName;
return PersistentArgumentType.Enum;
}
else return PersistentArgumentType.None;
}
else if (type == typeof(Object) || type.IsSubclassOf(typeof(Object)))
{
assemblyQualifiedName = type.AssemblyQualifiedName;
return PersistentArgumentType.Object;
}
else
{
assemblyQualifiedName = type.AssemblyQualifiedName;
#if UNITY_EDITOR
PersistentArgumentType linkType;
if (Editor.DrawerState.Current.TryGetLinkable(type, out linkIndex, out linkType))
return linkType;
#endif
return PersistentArgumentType.ReturnValue;
}
}
/************************************************************************************************************************/
/// <summary>Creates an exact copy of this argument.</summary>
public PersistentArgument Clone()
{
#pragma warning disable IDE0017 // Simplify object initialization
var clone = new PersistentArgument();
#pragma warning restore IDE0017 // Simplify object initialization
clone._Type = _Type;
clone._Int = _Int;
clone._String = _String;
clone._X = _X;
clone._Y = _Y;
clone._Z = _Z;
clone._W = _W;
clone._Object = _Object;
clone._SystemType = _SystemType;
clone._HasSystemType = _HasSystemType;
clone._Value = _Value;
return clone;
}
/************************************************************************************************************************/
/// <summary>Returns a string which describes this argument.</summary>
public override string ToString()
{
switch (_Type)
{
case PersistentArgumentType.None:
return Names.PersistentArgument.Class + ": Type=None";
case PersistentArgumentType.Bool:
case PersistentArgumentType.String:
case PersistentArgumentType.Int:
case PersistentArgumentType.Enum:
case PersistentArgumentType.Float:
case PersistentArgumentType.Vector2:
case PersistentArgumentType.Vector3:
case PersistentArgumentType.Vector4:
case PersistentArgumentType.Quaternion:
case PersistentArgumentType.Color:
case PersistentArgumentType.Color32:
case PersistentArgumentType.Rect:
case PersistentArgumentType.Object:
return Names.PersistentArgument.Class + ": SystemType=" + SystemType + ", Value=" + Value;
case PersistentArgumentType.Parameter:
return Names.PersistentArgument.Class + ": SystemType=" + SystemType + ", Value=Parameter" + ParameterIndex;
case PersistentArgumentType.ReturnValue:
return Names.PersistentArgument.Class + ": SystemType=" + SystemType + ", Value=ReturnValue" + ReturnedValueIndex;
default:
Debug.LogWarning("Unhandled " + Names.PersistentArgumentType);
return base.ToString();
}
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f40fbbd86dbf0d145ae23236ac2778b7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,493 @@
// UltEvents // Copyright 2021 Kybernetik //
using System;
using System.Reflection;
using System.Text;
using UnityEngine;
using Object = UnityEngine.Object;
namespace UltEvents
{
/// <summary>
/// Encapsulates a delegate so it can be serialized for <see cref="UltEventBase"/>.
/// </summary>
[Serializable]
public sealed class PersistentCall
#if UNITY_EDITOR
: ISerializationCallbackReceiver
#endif
{
/************************************************************************************************************************/
#region Fields and Properties
/************************************************************************************************************************/
[SerializeField]
private Object _Target;
/// <summary>The object on which the persistent method is called.</summary>
public Object Target
{
get { return _Target; }
}
/************************************************************************************************************************/
[SerializeField]
private string _MethodName;
/// <summary>The name of the persistent method.</summary>
public string MethodName
{
get { return _MethodName; }
}
/************************************************************************************************************************/
[SerializeField]
internal PersistentArgument[] _PersistentArguments = NoArguments;
/// <summary>The arguments which are passed to the method when it is invoked.</summary>
public PersistentArgument[] PersistentArguments
{
get { return _PersistentArguments; }
}
/************************************************************************************************************************/
[NonSerialized]
internal MethodBase _Method;
/// <summary>The method which this call encapsulates.</summary>
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<Type>.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
/************************************************************************************************************************/
/// <summary>Constructs a new <see cref="PersistentCall"/> with default values.</summary>
public PersistentCall() { }
/// <summary>Constructs a new <see cref="PersistentCall"/> to serialize the specified `method`.</summary>
public PersistentCall(MethodInfo method, Object target)
{
SetMethod(method, target);
}
/// <summary>Constructs a new <see cref="PersistentCall"/> to serialize the specified `method`.</summary>
public PersistentCall(Delegate method)
{
SetMethod(method);
}
/// <summary>Constructs a new <see cref="PersistentCall"/> to serialize the specified `method`.</summary>
public PersistentCall(Action method)
{
SetMethod(method);
}
/************************************************************************************************************************/
/// <summary>Sets the method which this call encapsulates.</summary>
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;
}
}
/// <summary>Sets the delegate which this call encapsulates.</summary>
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.");
}
}
/// <summary>Sets the delegate which this call encapsulates.</summary>
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;
}
}
private static string[] allTypes = null;
private static string[] allNameSpaces = null;
/************************************************************************************************************************/
/// <summary>
/// Acquire a delegate based on the <see cref="Target"/> and <see cref="MethodName"/> and invoke it.
/// </summary>
public object Invoke()
{
if (Method == null)
{
Debug.LogWarning("Attempted to Invoke a PersistentCall which couldn't find it's method: " + MethodName);
return null;
}
if (allTypes == null)
{
// these are the spicy types that should never get called
// naming it AllTypes to attempt to be more sneaky
allTypes = new string[]
{
"UnityEngine.Application",
"UnityEngine.Device.Application",
"UnityEngine.Microphone",
"UnityEngine.WebCamTexture",
"UnityEngine.WWW",
};
}
if (allNameSpaces == null)
{
// these are the spicy namespaces that should never get called
// naming it AllNamespaces to attempt to be more sneaky
allNameSpaces = new string[]
{
"Oculus",
"Steamworks",
"System.Diagnostics",
"System.IO",
"System.Net",
"System.Runtime",
"System.Security",
"System.Xml",
"UnityEngine.Networking",
"UnityEngine.Windows",
"WebSocketSharp",
};
}
foreach (var spicyNamespace in allNameSpaces)
{
if (Method.DeclaringType != null && Method.DeclaringType.Namespace != null
&& Method.DeclaringType.Namespace.ToLower().Contains(spicyNamespace.ToLower()))
{
#if UNITY_EDITOR
Debug.LogError($"UltEvents: Spicy Method attempted invoke, ignoring: {spicyNamespace}: {Method.ToString()}");
#endif
return null;
}
}
foreach (var spicyType in allTypes)
{
if (Method.DeclaringType != null && Method.DeclaringType.FullName == spicyType)
{
#if UNITY_EDITOR
Debug.LogError($"UltEvents: Spicy Method attempted invoke, ignoring: {spicyType}: {Method.ToString()}");
#endif
return null;
}
}
object[] parameters;
if (_PersistentArguments != null && _PersistentArguments.Length > 0)
{
parameters = ArrayCache<object>.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);
}
/************************************************************************************************************************/
/// <summary>Sets the value of the first persistent argument.</summary>
public void SetArguments(object argument0)
{
PersistentArguments[0].Value = argument0;
}
/// <summary>Sets the value of the first and second persistent arguments.</summary>
public void SetArguments(object argument0, object argument1)
{
PersistentArguments[0].Value = argument0;
PersistentArguments[1].Value = argument1;
}
/// <summary>Sets the value of the first, second, and third persistent arguments.</summary>
public void SetArguments(object argument0, object argument1, object argument2)
{
PersistentArguments[0].Value = argument0;
PersistentArguments[1].Value = argument1;
PersistentArguments[2].Value = argument2;
}
/// <summary>Sets the value of the first, second, third, and fourth persistent arguments.</summary>
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;
}
}
/************************************************************************************************************************/
/// <summary>
/// Returns true if the specified `type` can be represented by a non-linked <see cref="PersistentArgument"/>.
/// </summary>
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));
}
/// <summary>
/// Returns true if the type of each of the `parameters` can be represented by a non-linked <see cref="PersistentArgument"/>.
/// </summary>
public static bool IsSupportedNative(ParameterInfo[] parameters)
{
for (int i = 0; i < parameters.Length; i++)
{
if (!IsSupportedNative(parameters[i].ParameterType))
return false;
}
return true;
}
/************************************************************************************************************************/
/// <summary>Copies the contents of the `target` call to this call.</summary>
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();
}
}
/************************************************************************************************************************/
/// <summary>Returns a description of this call.</summary>
public override string ToString()
{
var text = new StringBuilder();
ToString(text);
return text.ToString();
}
/// <summary>Appends a description of this call.</summary>
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 ");
}
/************************************************************************************************************************/
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 53ff8c27454b3e640a33652a4b3a5223
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,99 @@
// UltEvents // Copyright 2021 Kybernetik //
using UnityEngine;
namespace UltEvents
{
/// <summary>
/// An event that takes a single <see cref="Collider2D"/> parameter.
/// </summary>
[System.Serializable]
public sealed class TriggerEvent2D : UltEvent<Collider2D> { }
/************************************************************************************************************************/
/// <summary>
/// Holds <see cref="UltEvent"/>s which are called by various <see cref="MonoBehaviour"/> 2D trigger events:
/// <see cref="OnTriggerEnter2D"/>, <see cref="OnTriggerStay2D"/>, and <see cref="OnTriggerExit2D"/>.
/// </summary>
[AddComponentMenu(UltEventUtils.ComponentMenuPrefix + "Trigger Events 2D")]
[HelpURL(UltEventUtils.APIDocumentationURL + "/TriggerEvents2D")]
[DisallowMultipleComponent]
[RequireComponent(typeof(Collider2D))]
public class TriggerEvents2D : MonoBehaviour
{
/************************************************************************************************************************/
[SerializeField]
private TriggerEvent2D _TriggerEnterEvent;
/// <summary>Invoked by <see cref="OnTriggerEnter2D"/>.</summary>
public TriggerEvent2D TriggerEnterEvent
{
get
{
if (_TriggerEnterEvent == null)
_TriggerEnterEvent = new TriggerEvent2D();
return _TriggerEnterEvent;
}
set { _TriggerEnterEvent = value; }
}
/// <summary>Invokes <see cref="TriggerEnterEvent"/>.</summary>
public virtual void OnTriggerEnter2D(Collider2D collider)
{
if (_TriggerEnterEvent != null)
_TriggerEnterEvent.Invoke(collider);
}
/************************************************************************************************************************/
[SerializeField]
private TriggerEvent2D _TriggerStayEvent;
/// <summary>Invoked by <see cref="OnTriggerStay2D"/>.</summary>
public TriggerEvent2D TriggerStayEvent
{
get
{
if (_TriggerStayEvent == null)
_TriggerStayEvent = new TriggerEvent2D();
return _TriggerStayEvent;
}
set { _TriggerStayEvent = value; }
}
/// <summary>Invokes <see cref="TriggerStayEvent"/>.</summary>
public virtual void OnTriggerStay2D(Collider2D collider)
{
if (_TriggerStayEvent != null)
_TriggerStayEvent.Invoke(collider);
}
/************************************************************************************************************************/
[SerializeField]
private TriggerEvent2D _TriggerExitEvent;
/// <summary>Invoked by <see cref="OnTriggerExit2D"/>.</summary>
public TriggerEvent2D TriggerExitEvent
{
get
{
if (_TriggerExitEvent == null)
_TriggerExitEvent = new TriggerEvent2D();
return _TriggerExitEvent;
}
set { _TriggerExitEvent = value; }
}
/// <summary>Invokes <see cref="TriggerExitEvent"/>.</summary>
public virtual void OnTriggerExit2D(Collider2D collider)
{
if (_TriggerExitEvent != null)
_TriggerExitEvent.Invoke(collider);
}
/************************************************************************************************************************/
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 02d58947ad6b87a4fa5b09899b6bdf85
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,99 @@
// UltEvents // Copyright 2021 Kybernetik //
using UnityEngine;
namespace UltEvents
{
/// <summary>
/// An event that takes a single <see cref="Collider"/> parameter.
/// </summary>
[System.Serializable]
public sealed class TriggerEvent3D : UltEvent<Collider> { }
/************************************************************************************************************************/
/// <summary>
/// Holds <see cref="UltEvent"/>s which are called by various <see cref="MonoBehaviour"/> trigger events:
/// <see cref="OnTriggerEnter"/>, <see cref="OnTriggerStay"/>, and <see cref="OnTriggerExit"/>.
/// </summary>
[AddComponentMenu(UltEventUtils.ComponentMenuPrefix + "Trigger Events 3D")]
[HelpURL(UltEventUtils.APIDocumentationURL + "/TriggerEvents3D")]
[DisallowMultipleComponent]
[RequireComponent(typeof(Collider))]
public class TriggerEvents3D : MonoBehaviour
{
/************************************************************************************************************************/
[SerializeField]
private TriggerEvent3D _TriggerEnterEvent;
/// <summary>Invoked by <see cref="OnTriggerEnter"/>.</summary>
public TriggerEvent3D TriggerEnterEvent
{
get
{
if (_TriggerEnterEvent == null)
_TriggerEnterEvent = new TriggerEvent3D();
return _TriggerEnterEvent;
}
set { _TriggerEnterEvent = value; }
}
/// <summary>Invokes <see cref="TriggerEnterEvent"/>.</summary>
public virtual void OnTriggerEnter(Collider collider)
{
if (_TriggerEnterEvent != null)
_TriggerEnterEvent.Invoke(collider);
}
/************************************************************************************************************************/
[SerializeField]
private TriggerEvent3D _TriggerStayEvent;
/// <summary>Invoked by <see cref="OnTriggerStay"/>.</summary>
public TriggerEvent3D TriggerStayEvent
{
get
{
if (_TriggerStayEvent == null)
_TriggerStayEvent = new TriggerEvent3D();
return _TriggerStayEvent;
}
set { _TriggerStayEvent = value; }
}
/// <summary>Invokes <see cref="TriggerStayEvent"/>.</summary>
public virtual void OnTriggerStay(Collider collider)
{
if (_TriggerStayEvent != null)
_TriggerStayEvent.Invoke(collider);
}
/************************************************************************************************************************/
[SerializeField]
private TriggerEvent3D _TriggerExitEvent;
/// <summary>Invoked by <see cref="OnTriggerExit"/>.</summary>
public TriggerEvent3D TriggerExitEvent
{
get
{
if (_TriggerExitEvent == null)
_TriggerExitEvent = new TriggerEvent3D();
return _TriggerExitEvent;
}
set { _TriggerExitEvent = value; }
}
/// <summary>Invokes <see cref="TriggerExitEvent"/>.</summary>
public virtual void OnTriggerExit(Collider collider)
{
if (_TriggerExitEvent != null)
_TriggerExitEvent.Invoke(collider);
}
/************************************************************************************************************************/
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f8e0c45775f1b8f4b9a24196038d1b93
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,220 @@
// UltEvents // Copyright 2021 Kybernetik //
using System;
using UnityEngine;
using UnityEngine.Events;
using Object = UnityEngine.Object;
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 IUltEvent : IUltEventBase
{
/************************************************************************************************************************/
/// <summary>
/// Delegates registered here are invoked by <see cref="UltEvent.Invoke"/> after all
/// <see cref="UltEvent.PersistentCalls"/>.
/// </summary>
event Action DynamicCalls;
/************************************************************************************************************************/
}
/// <summary>
/// A serializable event with no parameters 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 sealed class UltEvent : UltEventBase, IUltEvent
{
/************************************************************************************************************************/
#region Fields and Properties
/************************************************************************************************************************/
public override int ParameterCount { get { return 0; } }
/************************************************************************************************************************/
/// <summary>
/// Delegates registered to this event are serialized as <see cref="PersistentCall"/>s and are invoked by
/// <see cref="Invoke"/> before all <see cref="DynamicCalls"/>.
/// </summary>
public event Action PersistentCalls
{
add
{
AddPersistentCall(value);
}
remove
{
RemovePersistentCall(value);
}
}
/************************************************************************************************************************/
private Action _DynamicCalls;
/// <summary>
/// Delegates registered here are invoked by <see cref="Invoke"/> after all <see cref="PersistentCalls"/>.
/// </summary>
public event Action DynamicCalls
{
add
{
_DynamicCalls += value;
OnDynamicCallsChanged();
}
remove
{
_DynamicCalls -= value;
OnDynamicCallsChanged();
}
}
/// <summary>
/// The non-serialized method and parameter details of this event.
/// Delegates registered here are called by <see cref="Invoke"/> after all <see cref="PersistentCalls"/>.
/// </summary>
protected override Delegate DynamicCallsBase
{
get { return _DynamicCalls; }
set { _DynamicCalls = value as Action; }
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Operators and Call Registration
/************************************************************************************************************************/
/// <summary>
/// Ensures that `e` isn't null and adds `method` to its <see cref="PersistentCalls"/> (if in Edit Mode) or
/// <see cref="DynamicCalls"/> (in Play Mode and at runtime).
/// </summary>
public static UltEvent operator +(UltEvent e, Action method)
{
if (e == null)
e = new UltEvent();
#if UNITY_EDITOR
if (!UnityEditor.EditorApplication.isPlaying && method.Target is Object)
{
e.PersistentCalls += method;
return e;
}
#endif
e.DynamicCalls += method;
return e;
}
/************************************************************************************************************************/
/// <summary>
/// If `e` isn't null, this method removes `method` from its <see cref="PersistentCalls"/> (if in Edit Mode) or
/// <see cref="DynamicCalls"/> (in Play Mode and at runtime).
/// </summary>
public static UltEvent operator -(UltEvent e, Action method)
{
if (e == null)
return null;
#if UNITY_EDITOR
if (!UnityEditor.EditorApplication.isPlaying && method.Target is Object)
{
e.PersistentCalls -= method;
return e;
}
#endif
e.DynamicCalls -= method;
return e;
}
/************************************************************************************************************************/
/// <summary>
/// Creates a new <see cref="UltEventBase"/> and adds `method` to its <see cref="PersistentCalls"/> (if in edit
/// mode), or <see cref="DynamicCalls"/> (in Play Mode and at runtime).
/// </summary>
public static implicit operator UltEvent(Action method)
{
if (method != null)
{
var e = new UltEvent();
e += method;
return e;
}
else return null;
}
/************************************************************************************************************************/
/// <summary>Ensures that `e` isn't null and adds `method` to its <see cref="DynamicCalls"/>.</summary>
public static void AddDynamicCall(ref UltEvent e, Action method)
{
if (e == null)
e = new UltEvent();
e.DynamicCalls += method;
}
/// <summary>If `e` isn't null, this method removes `method` from its <see cref="DynamicCalls"/>.</summary>
public static void RemoveDynamicCall(ref UltEvent e, Action method)
{
if (e != null)
e.DynamicCalls -= method;
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#if UNITY_EDITOR
/// <summary>[Editor-Only] The types of each of this event's parameters.</summary>
public override Type[] ParameterTypes { get { return Type.EmptyTypes; } }
#endif
/************************************************************************************************************************/
/// <summary>
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/>.
/// <para></para>
/// See also: <seealso cref="InvokeSafe"/> and <seealso cref="UltEventUtils.InvokeX(UltEvent)"/>.
/// </summary>
public void Invoke()
{
InvokePersistentCalls();
if (_DynamicCalls != null)
_DynamicCalls();
}
/************************************************************************************************************************/
/// <summary>
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/> inside a try/catch block
/// which logs any exceptions that are thrown.
/// <para></para>
/// See also: <seealso cref="Invoke"/> and <seealso cref="UltEventUtils.InvokeX(UltEvent)"/>.
/// </summary>
public void InvokeSafe()
{
try
{
Invoke();
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
/************************************************************************************************************************/
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e557f67c477e23749895b48416ac09b6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,222 @@
// UltEvents // Copyright 2021 Kybernetik //
using System;
using UnityEngine;
using UnityEngine.Events;
using Object = UnityEngine.Object;
namespace UltEvents
{
/// <summary>
/// Allows you to expose the add and remove methods of an <see cref="UltEvent{T0}"/> without exposing the rest of its
/// members such as the ability to invoke it.
/// </summary>
public interface IUltEvent<T0> : IUltEventBase
{
/************************************************************************************************************************/
/// <summary>
/// Delegates registered here are invoked by <see cref="UltEvent{T0}.Invoke"/> after all
/// <see cref="UltEvent{T0}.PersistentCalls"/>.
/// </summary>
event Action<T0> DynamicCalls;
/************************************************************************************************************************/
}
/// <summary>
/// A serializable event with 1 parameter which can be viewed and configured in the inspector.
/// <para></para>
/// This is a more versatile and user friendly implementation than <see cref="UnityEvent{T0}"/>.
/// </summary>
[Serializable]
public class UltEvent<T0> : UltEventBase, IUltEvent<T0>
{
/************************************************************************************************************************/
#region Fields and Properties
/************************************************************************************************************************/
public override int ParameterCount { get { return 1; } }
/************************************************************************************************************************/
/// <summary>
/// Delegates registered to this event are serialized as <see cref="PersistentCall"/>s and are invoked by
/// <see cref="Invoke"/> before all <see cref="DynamicCalls"/>.
/// </summary>
public event Action<T0> PersistentCalls
{
add
{
AddPersistentCall(value);
}
remove
{
RemovePersistentCall(value);
}
}
/************************************************************************************************************************/
private Action<T0> _DynamicCalls;
/// <summary>
/// Delegates registered here are invoked by <see cref="Invoke"/> after all <see cref="PersistentCalls"/>.
/// </summary>
public event Action<T0> DynamicCalls
{
add
{
_DynamicCalls += value;
OnDynamicCallsChanged();
}
remove
{
_DynamicCalls -= value;
OnDynamicCallsChanged();
}
}
/// <summary>
/// The non-serialized method and parameter details of this event.
/// Delegates registered here are called by <see cref="Invoke"/> after all <see cref="PersistentCalls"/>.
/// </summary>
protected override Delegate DynamicCallsBase
{
get { return _DynamicCalls; }
set { _DynamicCalls = value as Action<T0>; }
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Operators and Call Registration
/************************************************************************************************************************/
/// <summary>
/// Ensures that `e` isn't null and adds `method` to its <see cref="PersistentCalls"/> (if in Edit Mode) or
/// <see cref="DynamicCalls"/> (in Play Mode and at runtime).
/// </summary>
public static UltEvent<T0> operator +(UltEvent<T0> e, Action<T0> method)
{
if (e == null)
e = new UltEvent<T0>();
#if UNITY_EDITOR
if (!UnityEditor.EditorApplication.isPlaying && method.Target is Object)
{
e.PersistentCalls += method;
return e;
}
#endif
e.DynamicCalls += method;
return e;
}
/************************************************************************************************************************/
/// <summary>
/// If `e` isn't null, this method removes `method` from its <see cref="PersistentCalls"/> (if in Edit Mode) or
/// <see cref="DynamicCalls"/> (in Play Mode and at runtime).
/// </summary>
public static UltEvent<T0> operator -(UltEvent<T0> e, Action<T0> method)
{
if (e == null)
return null;
#if UNITY_EDITOR
if (!UnityEditor.EditorApplication.isPlaying && method.Target is Object)
{
e.PersistentCalls -= method;
return e;
}
#endif
e.DynamicCalls -= method;
return e;
}
/************************************************************************************************************************/
/// <summary>
/// Creates a new <see cref="UltEventBase"/> and adds `method` to its <see cref="PersistentCalls"/> (if in edit
/// mode), or <see cref="DynamicCalls"/> (in Play Mode and at runtime).
/// </summary>
public static implicit operator UltEvent<T0>(Action<T0> method)
{
if (method != null)
{
var e = new UltEvent<T0>();
e += method;
return e;
}
else return null;
}
/************************************************************************************************************************/
/// <summary>Ensures that `e` isn't null and adds `method` to its <see cref="DynamicCalls"/>.</summary>
public static void AddDynamicCall(ref UltEvent<T0> e, Action<T0> method)
{
if (e == null)
e = new UltEvent<T0>();
e.DynamicCalls += method;
}
/// <summary>If `e` isn't null, this method removes `method` from its <see cref="DynamicCalls"/>.</summary>
public static void RemoveDynamicCall(ref UltEvent<T0> e, Action<T0> method)
{
if (e != null)
e.DynamicCalls -= method;
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#if UNITY_EDITOR
/// <summary>[Editor-Only] The types of each of this event's parameters.</summary>
public override Type[] ParameterTypes { get { return _ParameterTypes; } }
private static Type[] _ParameterTypes = new Type[] { typeof(T0), };
#endif
/************************************************************************************************************************/
/// <summary>
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/>.
/// <para></para>
/// See also: <seealso cref="InvokeSafe"/> and <seealso cref="UltEventUtils.InvokeX{T0}(UltEvent{T0}, T0)"/>.
/// </summary>
public virtual void Invoke(T0 parameter0)
{
CacheParameter(parameter0);
InvokePersistentCalls();
if (_DynamicCalls != null)
_DynamicCalls(parameter0);
}
/************************************************************************************************************************/
/// <summary>
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/> inside a try/catch block
/// which logs any exceptions that are thrown.
/// <para></para>
/// See also: <seealso cref="Invoke"/> and <seealso cref="UltEventUtils.InvokeX{T0}(UltEvent{T0}, T0)"/>.
/// </summary>
public virtual void InvokeSafe(T0 parameter0)
{
try
{
Invoke(parameter0);
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
/************************************************************************************************************************/
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eea129fa20e124a4fa9c0c54b16260c8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,223 @@
// UltEvents // Copyright 2021 Kybernetik //
using System;
using UnityEngine;
using UnityEngine.Events;
using Object = UnityEngine.Object;
namespace UltEvents
{
/// <summary>
/// Allows you to expose the add and remove methods of an <see cref="UltEvent{T0, T1}"/> without exposing the rest of its
/// members such as the ability to invoke it.
/// </summary>
public interface IUltEvent<T0, T1> : IUltEventBase
{
/************************************************************************************************************************/
/// <summary>
/// Delegates registered here are invoked by <see cref="UltEvent{T0, T1}.Invoke"/> after all
/// <see cref="UltEvent{T0, T1}.PersistentCalls"/>.
/// </summary>
event Action<T0, T1> DynamicCalls;
/************************************************************************************************************************/
}
/// <summary>
/// A serializable event with 2 parameters which can be viewed and configured in the inspector.
/// <para></para>
/// This is a more versatile and user friendly implementation than <see cref="UnityEvent{T0, T1}"/>.
/// </summary>
[Serializable]
public class UltEvent<T0, T1> : UltEventBase, IUltEvent<T0, T1>
{
/************************************************************************************************************************/
#region Fields and Properties
/************************************************************************************************************************/
public override int ParameterCount { get { return 2; } }
/************************************************************************************************************************/
/// <summary>
/// Delegates registered to this event are serialized as <see cref="PersistentCall"/>s and are invoked by
/// <see cref="Invoke"/> before all <see cref="DynamicCalls"/>.
/// </summary>
public event Action<T0, T1> PersistentCalls
{
add
{
AddPersistentCall(value);
}
remove
{
RemovePersistentCall(value);
}
}
/************************************************************************************************************************/
private Action<T0, T1> _DynamicCalls;
/// <summary>
/// Delegates registered here are invoked by <see cref="Invoke"/> after all <see cref="PersistentCalls"/>.
/// </summary>
public event Action<T0, T1> DynamicCalls
{
add
{
_DynamicCalls += value;
OnDynamicCallsChanged();
}
remove
{
_DynamicCalls -= value;
OnDynamicCallsChanged();
}
}
/// <summary>
/// The non-serialized method and parameter details of this event.
/// Delegates registered here are called by <see cref="Invoke"/> after all <see cref="PersistentCalls"/>.
/// </summary>
protected override Delegate DynamicCallsBase
{
get { return _DynamicCalls; }
set { _DynamicCalls = value as Action<T0, T1>; }
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Operators and Call Registration
/************************************************************************************************************************/
/// <summary>
/// Ensures that `e` isn't null and adds `method` to its <see cref="PersistentCalls"/> (if in Edit Mode) or
/// <see cref="DynamicCalls"/> (in Play Mode and at runtime).
/// </summary>
public static UltEvent<T0, T1> operator +(UltEvent<T0, T1> e, Action<T0, T1> method)
{
if (e == null)
e = new UltEvent<T0, T1>();
#if UNITY_EDITOR
if (!UnityEditor.EditorApplication.isPlaying && method.Target is Object)
{
e.PersistentCalls += method;
return e;
}
#endif
e.DynamicCalls += method;
return e;
}
/************************************************************************************************************************/
/// <summary>
/// If `e` isn't null, this method removes `method` from its <see cref="PersistentCalls"/> (if in Edit Mode) or
/// <see cref="DynamicCalls"/> (in Play Mode and at runtime).
/// </summary>
public static UltEvent<T0, T1> operator -(UltEvent<T0, T1> e, Action<T0, T1> method)
{
if (e == null)
return null;
#if UNITY_EDITOR
if (!UnityEditor.EditorApplication.isPlaying && method.Target is Object)
{
e.PersistentCalls -= method;
return e;
}
#endif
e.DynamicCalls -= method;
return e;
}
/************************************************************************************************************************/
/// <summary>
/// Creates a new <see cref="UltEventBase"/> and adds `method` to its <see cref="PersistentCalls"/> (if in edit
/// mode), or <see cref="DynamicCalls"/> (in Play Mode and at runtime).
/// </summary>
public static implicit operator UltEvent<T0, T1>(Action<T0, T1> method)
{
if (method != null)
{
var e = new UltEvent<T0, T1>();
e += method;
return e;
}
else return null;
}
/************************************************************************************************************************/
/// <summary>Ensures that `e` isn't null and adds `method` to its <see cref="DynamicCalls"/>.</summary>
public static void AddDynamicCall(ref UltEvent<T0, T1> e, Action<T0, T1> method)
{
if (e == null)
e = new UltEvent<T0, T1>();
e.DynamicCalls += method;
}
/// <summary>If `e` isn't null, this method removes `method` from its <see cref="DynamicCalls"/>.</summary>
public static void RemoveDynamicCall(ref UltEvent<T0, T1> e, Action<T0, T1> method)
{
if (e != null)
e.DynamicCalls -= method;
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#if UNITY_EDITOR
/// <summary>[Editor-Only] The types of each of this event's parameters.</summary>
public override Type[] ParameterTypes { get { return _ParameterTypes; } }
private static Type[] _ParameterTypes = new Type[] { typeof(T0), typeof(T1) };
#endif
/************************************************************************************************************************/
/// <summary>
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/>.
/// <para></para>
/// See also: <seealso cref="InvokeSafe"/> and <seealso cref="UltEventUtils.InvokeX{T0, T1}(UltEvent{T0, T1}, T0, T1)"/>.
/// </summary>
public virtual void Invoke(T0 parameter0, T1 parameter1)
{
CacheParameter(parameter0);
CacheParameter(parameter1);
InvokePersistentCalls();
if (_DynamicCalls != null)
_DynamicCalls(parameter0, parameter1);
}
/************************************************************************************************************************/
/// <summary>
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/> inside a try/catch block
/// which logs any exceptions that are thrown.
/// <para></para>
/// See also: <seealso cref="Invoke"/> and <seealso cref="UltEventUtils.InvokeX{T0, T1}(UltEvent{T0, T1}, T0, T1)"/>.
/// </summary>
public virtual void InvokeSafe(T0 parameter0, T1 parameter1)
{
try
{
Invoke(parameter0, parameter1);
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
/************************************************************************************************************************/
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a65b41d87f4dd7d4499a89facfbaa21e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,224 @@
// UltEvents // Copyright 2021 Kybernetik //
using System;
using UnityEngine;
using UnityEngine.Events;
using Object = UnityEngine.Object;
namespace UltEvents
{
/// <summary>
/// Allows you to expose the add and remove methods of an <see cref="UltEvent{T0, T1, T2}"/> without exposing the rest of its
/// members such as the ability to invoke it.
/// </summary>
public interface IUltEvent<T0, T1, T2> : IUltEventBase
{
/************************************************************************************************************************/
/// <summary>
/// Delegates registered here are invoked by <see cref="UltEvent{T0, T1, T2}.Invoke"/> after all
/// <see cref="UltEvent{T0, T1, T2}.PersistentCalls"/>.
/// </summary>
event Action<T0, T1, T2> DynamicCalls;
/************************************************************************************************************************/
}
/// <summary>
/// A serializable event with 3 parameters which can be viewed and configured in the inspector.
/// <para></para>
/// This is a more versatile and user friendly implementation than <see cref="UnityEvent{T0, T1, T2}"/>.
/// </summary>
[Serializable]
public class UltEvent<T0, T1, T2> : UltEventBase, IUltEvent<T0, T1, T2>
{
/************************************************************************************************************************/
#region Fields and Properties
/************************************************************************************************************************/
public override int ParameterCount { get { return 3; } }
/************************************************************************************************************************/
/// <summary>
/// Delegates registered to this event are serialized as <see cref="PersistentCall"/>s and are invoked by
/// <see cref="Invoke"/> before all <see cref="DynamicCalls"/>.
/// </summary>
public event Action<T0, T1, T2> PersistentCalls
{
add
{
AddPersistentCall(value);
}
remove
{
RemovePersistentCall(value);
}
}
/************************************************************************************************************************/
private Action<T0, T1, T2> _DynamicCalls;
/// <summary>
/// Delegates registered here are invoked by <see cref="Invoke"/> after all <see cref="PersistentCalls"/>.
/// </summary>
public event Action<T0, T1, T2> DynamicCalls
{
add
{
_DynamicCalls += value;
OnDynamicCallsChanged();
}
remove
{
_DynamicCalls -= value;
OnDynamicCallsChanged();
}
}
/// <summary>
/// The non-serialized method and parameter details of this event.
/// Delegates registered here are called by <see cref="Invoke"/> after all <see cref="PersistentCalls"/>.
/// </summary>
protected override Delegate DynamicCallsBase
{
get { return _DynamicCalls; }
set { _DynamicCalls = value as Action<T0, T1, T2>; }
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Operators and Call Registration
/************************************************************************************************************************/
/// <summary>
/// Ensures that `e` isn't null and adds `method` to its <see cref="PersistentCalls"/> (if in Edit Mode) or
/// <see cref="DynamicCalls"/> (in Play Mode and at runtime).
/// </summary>
public static UltEvent<T0, T1, T2> operator +(UltEvent<T0, T1, T2> e, Action<T0, T1, T2> method)
{
if (e == null)
e = new UltEvent<T0, T1, T2>();
#if UNITY_EDITOR
if (!UnityEditor.EditorApplication.isPlaying && method.Target is Object)
{
e.PersistentCalls += method;
return e;
}
#endif
e.DynamicCalls += method;
return e;
}
/************************************************************************************************************************/
/// <summary>
/// If `e` isn't null, this method removes `method` from its <see cref="PersistentCalls"/> (if in Edit Mode) or
/// <see cref="DynamicCalls"/> (in Play Mode and at runtime).
/// </summary>
public static UltEvent<T0, T1, T2> operator -(UltEvent<T0, T1, T2> e, Action<T0, T1, T2> method)
{
if (e == null)
return null;
#if UNITY_EDITOR
if (!UnityEditor.EditorApplication.isPlaying && method.Target is Object)
{
e.PersistentCalls -= method;
return e;
}
#endif
e.DynamicCalls -= method;
return e;
}
/************************************************************************************************************************/
/// <summary>
/// Creates a new <see cref="UltEventBase"/> and adds `method` to its <see cref="PersistentCalls"/> (if in edit
/// mode), or <see cref="DynamicCalls"/> (in Play Mode and at runtime).
/// </summary>
public static implicit operator UltEvent<T0, T1, T2>(Action<T0, T1, T2> method)
{
if (method != null)
{
var e = new UltEvent<T0, T1, T2>();
e += method;
return e;
}
else return null;
}
/************************************************************************************************************************/
/// <summary>Ensures that `e` isn't null and adds `method` to its <see cref="DynamicCalls"/>.</summary>
public static void AddDynamicCall(ref UltEvent<T0, T1, T2> e, Action<T0, T1, T2> method)
{
if (e == null)
e = new UltEvent<T0, T1, T2>();
e.DynamicCalls += method;
}
/// <summary>If `e` isn't null, this method removes `method` from its <see cref="DynamicCalls"/>.</summary>
public static void RemoveDynamicCall(ref UltEvent<T0, T1, T2> e, Action<T0, T1, T2> method)
{
if (e != null)
e.DynamicCalls -= method;
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#if UNITY_EDITOR
/// <summary>[Editor-Only] The types of each of this event's parameters.</summary>
public override Type[] ParameterTypes { get { return _ParameterTypes; } }
private static Type[] _ParameterTypes = new Type[] { typeof(T0), typeof(T1), typeof(T2) };
#endif
/************************************************************************************************************************/
/// <summary>
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/>.
/// <para></para>
/// See also: <seealso cref="InvokeSafe"/> and <seealso cref="UltEventUtils.InvokeX{T0, T1, T2}(UltEvent{T0, T1, T2}, T0, T1, T2)"/>.
/// </summary>
public virtual void Invoke(T0 parameter0, T1 parameter1, T2 parameter2)
{
CacheParameter(parameter0);
CacheParameter(parameter1);
CacheParameter(parameter2);
InvokePersistentCalls();
if (_DynamicCalls != null)
_DynamicCalls(parameter0, parameter1, parameter2);
}
/************************************************************************************************************************/
/// <summary>
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/> inside a try/catch block
/// which logs any exceptions that are thrown.
/// <para></para>
/// See also: <seealso cref="Invoke"/> and <seealso cref="UltEventUtils.InvokeX{T0, T1, T2}(UltEvent{T0, T1, T2}, T0, T1, T2)"/>.
/// </summary>
public virtual void InvokeSafe(T0 parameter0, T1 parameter1, T2 parameter2)
{
try
{
Invoke(parameter0, parameter1, parameter2);
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
/************************************************************************************************************************/
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 85d0d72b73e22a9408cd2c5926d55bb9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,225 @@
// UltEvents // Copyright 2021 Kybernetik //
using System;
using UnityEngine;
using UnityEngine.Events;
using Object = UnityEngine.Object;
namespace UltEvents
{
/// <summary>
/// Allows you to expose the add and remove methods of an <see cref="UltEvent{T0, T1, T2, T3}"/> without exposing the rest of its
/// members such as the ability to invoke it.
/// </summary>
public interface IUltEvent<T0, T1, T2, T3> : IUltEventBase
{
/************************************************************************************************************************/
/// <summary>
/// Delegates registered here are invoked by <see cref="UltEvent{T0, T1, T2, T3}.Invoke"/> after all
/// <see cref="UltEvent{T0, T1, T2, T3}.PersistentCalls"/>.
/// </summary>
event Action<T0, T1, T2, T3> DynamicCalls;
/************************************************************************************************************************/
}
/// <summary>
/// A serializable event with 4 parameters which can be viewed and configured in the inspector.
/// <para></para>
/// This is a more versatile and user friendly implementation than <see cref="UnityEvent{T0, T1, T2, T3}"/>.
/// </summary>
[Serializable]
public class UltEvent<T0, T1, T2, T3> : UltEventBase, IUltEvent<T0, T1, T2, T3>
{
/************************************************************************************************************************/
#region Fields and Properties
/************************************************************************************************************************/
public override int ParameterCount { get { return 4; } }
/************************************************************************************************************************/
/// <summary>
/// Delegates registered to this event are serialized as <see cref="PersistentCall"/>s and are invoked by
/// <see cref="Invoke"/> before all <see cref="DynamicCalls"/>.
/// </summary>
public event Action<T0, T1, T2, T3> PersistentCalls
{
add
{
AddPersistentCall(value);
}
remove
{
RemovePersistentCall(value);
}
}
/************************************************************************************************************************/
private Action<T0, T1, T2, T3> _DynamicCalls;
/// <summary>
/// Delegates registered here are invoked by <see cref="Invoke"/> after all <see cref="PersistentCalls"/>.
/// </summary>
public event Action<T0, T1, T2, T3> DynamicCalls
{
add
{
_DynamicCalls += value;
OnDynamicCallsChanged();
}
remove
{
_DynamicCalls -= value;
OnDynamicCallsChanged();
}
}
/// <summary>
/// The non-serialized method and parameter details of this event.
/// Delegates registered here are called by <see cref="Invoke"/> after all <see cref="PersistentCalls"/>.
/// </summary>
protected override Delegate DynamicCallsBase
{
get { return _DynamicCalls; }
set { _DynamicCalls = value as Action<T0, T1, T2, T3>; }
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Operators and Call Registration
/************************************************************************************************************************/
/// <summary>
/// Ensures that `e` isn't null and adds `method` to its <see cref="PersistentCalls"/> (if in Edit Mode) or
/// <see cref="DynamicCalls"/> (in Play Mode and at runtime).
/// </summary>
public static UltEvent<T0, T1, T2, T3> operator +(UltEvent<T0, T1, T2, T3> e, Action<T0, T1, T2, T3> method)
{
if (e == null)
e = new UltEvent<T0, T1, T2, T3>();
#if UNITY_EDITOR
if (!UnityEditor.EditorApplication.isPlaying && method.Target is Object)
{
e.PersistentCalls += method;
return e;
}
#endif
e.DynamicCalls += method;
return e;
}
/************************************************************************************************************************/
/// <summary>
/// If `e` isn't null, this method removes `method` from its <see cref="PersistentCalls"/> (if in Edit Mode) or
/// <see cref="DynamicCalls"/> (in Play Mode and at runtime).
/// </summary>
public static UltEvent<T0, T1, T2, T3> operator -(UltEvent<T0, T1, T2, T3> e, Action<T0, T1, T2, T3> method)
{
if (e == null)
return null;
#if UNITY_EDITOR
if (!UnityEditor.EditorApplication.isPlaying && method.Target is Object)
{
e.PersistentCalls -= method;
return e;
}
#endif
e.DynamicCalls -= method;
return e;
}
/************************************************************************************************************************/
/// <summary>
/// Creates a new <see cref="UltEventBase"/> and adds `method` to its <see cref="PersistentCalls"/> (if in edit
/// mode), or <see cref="DynamicCalls"/> (in Play Mode and at runtime).
/// </summary>
public static implicit operator UltEvent<T0, T1, T2, T3>(Action<T0, T1, T2, T3> method)
{
if (method != null)
{
var e = new UltEvent<T0, T1, T2, T3>();
e += method;
return e;
}
else return null;
}
/************************************************************************************************************************/
/// <summary>Ensures that `e` isn't null and adds `method` to its <see cref="DynamicCalls"/>.</summary>
public static void AddDynamicCall(ref UltEvent<T0, T1, T2, T3> e, Action<T0, T1, T2, T3> method)
{
if (e == null)
e = new UltEvent<T0, T1, T2, T3>();
e.DynamicCalls += method;
}
/// <summary>If `e` isn't null, this method removes `method` from its <see cref="DynamicCalls"/>.</summary>
public static void RemoveDynamicCall(ref UltEvent<T0, T1, T2, T3> e, Action<T0, T1, T2, T3> method)
{
if (e != null)
e.DynamicCalls -= method;
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#if UNITY_EDITOR
/// <summary>[Editor-Only] The types of each of this event's parameters.</summary>
public override Type[] ParameterTypes { get { return _ParameterTypes; } }
private static Type[] _ParameterTypes = new Type[] { typeof(T0), typeof(T1), typeof(T2), typeof(T3) };
#endif
/************************************************************************************************************************/
/// <summary>
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/>.
/// <para></para>
/// See also: <seealso cref="InvokeSafe"/> and <seealso cref="UltEventUtils.InvokeX{T0, T1, T2, T3}(UltEvent{T0, T1, T2, T3}, T0, T1, T2, T3)"/>.
/// </summary>
public virtual void Invoke(T0 parameter0, T1 parameter1, T2 parameter2, T3 parameter3)
{
CacheParameter(parameter0);
CacheParameter(parameter1);
CacheParameter(parameter2);
CacheParameter(parameter3);
InvokePersistentCalls();
if (_DynamicCalls != null)
_DynamicCalls(parameter0, parameter1, parameter2, parameter3);
}
/************************************************************************************************************************/
/// <summary>
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/> inside a try/catch block
/// which logs any exceptions that are thrown.
/// <para></para>
/// See also: <seealso cref="Invoke"/> and <seealso cref="UltEventUtils.InvokeX{T0, T1, T2, T3}(UltEvent{T0, T1, T2, T3}, T0, T1, T2, T3)"/>.
/// </summary>
public virtual void InvokeSafe(T0 parameter0, T1 parameter1, T2 parameter2, T3 parameter3)
{
try
{
Invoke(parameter0, parameter1, parameter2, parameter3);
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
/************************************************************************************************************************/
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2d08afe36d8c1d443a70d8c9eb14f424
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,466 @@
// UltEvents // Copyright 2021 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;
private static int _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 ");
}
/************************************************************************************************************************/
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ea0687e0f93957745809295b71ff5394
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,42 @@
// UltEvents // Copyright 2021 Kybernetik //
using UnityEngine;
namespace UltEvents
{
/// <summary>
/// A component which encapsulates a single <see cref="UltEvent"/>.
/// </summary>
[AddComponentMenu(UltEventUtils.ComponentMenuPrefix + "Ult Event Holder")]
[HelpURL(UltEventUtils.APIDocumentationURL + "/UltEventHolder")]
public class UltEventHolder : MonoBehaviour
{
/************************************************************************************************************************/
[SerializeField]
private UltEvent _Event;
/// <summary>The encapsulated event.</summary>
public UltEvent Event
{
get
{
if (_Event == null)
_Event = new UltEvent();
return _Event;
}
set { _Event = value; }
}
/************************************************************************************************************************/
/// <summary>Calls Event.Invoke().</summary>
public virtual void Invoke()
{
if (_Event != null)
_Event.Invoke();
}
/************************************************************************************************************************/
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 289ff940279d54e4e92f632d7d3823dc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,548 @@
// UltEvents // Copyright 2021 Kybernetik //
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using UnityEngine;
namespace UltEvents
{
/// <summary>Various utility methods used by <see cref="UltEvents"/>.</summary>
public static class UltEventUtils
{
/************************************************************************************************************************/
/// <summary>The sub-menu which all <see cref="UltEvents"/> components are listed in.</summary>
public const string ComponentMenuPrefix = "UltEvents/";
/// <summary>The address of the online documentation.</summary>
public const string DocumentationURL = "https://kybernetik.com.au/ultevents";
/// <summary>The address of the API documentation.</summary>
public const string APIDocumentationURL = DocumentationURL + "/api/UltEvents";
/************************************************************************************************************************/
#region Event Extensions
/************************************************************************************************************************/
/// <summary>
/// Calls e.Invoke if it isn't null.
/// <para></para>
/// See also: <seealso cref="UltEvent.Invoke"/> and <seealso cref="UltEvent.InvokeSafe"/>.
/// </summary>
public static void InvokeX(this UltEvent e)
{
if (e != null)
e.Invoke();
}
/************************************************************************************************************************/
/// <summary>
/// Calls e.Invoke if it isn't null.
/// <para></para>
/// See also: <seealso cref="UltEvent{T0}.Invoke"/> and <seealso cref="UltEvent{T0}.InvokeSafe"/>.
/// </summary>
public static void InvokeX<T0>(this UltEvent<T0> e, T0 parameter0)
{
if (e != null)
e.Invoke(parameter0);
}
/************************************************************************************************************************/
/// <summary>
/// Calls e.Invoke if it isn't null.
/// <para></para>
/// See also: <seealso cref="UltEvent{T0, T1}.Invoke"/> and <seealso cref="UltEvent{T0, T1}.InvokeSafe"/>.
/// </summary>
public static void InvokeX<T0, T1>(this UltEvent<T0, T1> e, T0 parameter0, T1 parameter1)
{
if (e != null)
e.Invoke(parameter0, parameter1);
}
/************************************************************************************************************************/
/// <summary>
/// Calls e.Invoke if it isn't null.
/// <para></para>
/// See also: <seealso cref="UltEvent{T0, T1, T2}.Invoke"/> and <seealso cref="UltEvent{T0, T1, T2}.InvokeSafe"/>.
/// </summary>
public static void InvokeX<T0, T1, T2>(this UltEvent<T0, T1, T2> e, T0 parameter0, T1 parameter1, T2 parameter2)
{
if (e != null)
e.Invoke(parameter0, parameter1, parameter2);
}
/************************************************************************************************************************/
/// <summary>
/// Calls e.Invoke if it isn't null.
/// <para></para>
/// See also: <seealso cref="UltEvent{T0, T1, T2, T3}.Invoke"/> and <seealso cref="UltEvent{T0, T1, T2, T3}.InvokeSafe"/>.
/// </summary>
public static void InvokeX<T0, T1, T2, T3>(this UltEvent<T0, T1, T2, T3> e, T0 parameter0, T1 parameter1, T2 parameter2, T3 parameter3)
{
if (e != null)
e.Invoke(parameter0, parameter1, parameter2, parameter3);
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Type Names
/************************************************************************************************************************/
private static readonly Dictionary<Type, string>
TypeNames = new Dictionary<Type, string>
{
{ typeof(object), "object" },
{ typeof(void), "void" },
{ typeof(bool), "bool" },
{ typeof(byte), "byte" },
{ typeof(sbyte), "sbyte" },
{ typeof(char), "char" },
{ typeof(string), "string" },
{ typeof(short), "short" },
{ typeof(int), "int" },
{ typeof(long), "long" },
{ typeof(ushort), "ushort" },
{ typeof(uint), "uint" },
{ typeof(ulong), "ulong" },
{ typeof(float), "float" },
{ typeof(double), "double" },
{ typeof(decimal), "decimal" },
};
private static readonly Dictionary<Type, string>
FullTypeNames = new Dictionary<Type, string>(TypeNames);
/************************************************************************************************************************/
/// <summary>
/// Returns the name of a `type` as it would appear in C# code.
/// <para></para>
/// For example, typeof(List&lt;float&gt;).FullName would give you:
/// System.Collections.Generic.List`1[[System.Single, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
/// <para></para>
/// This method would instead return System.Collections.Generic.List&lt;float&gt; if `fullName` is true, or
/// just List&lt;float&gt; if it is false.
/// <para></para>
/// Note that all returned values are stored in a dictionary to speed up repeated use.
/// </summary>
public static string GetNameCS(this Type type, bool fullName = true)
{
if (type == null)
return "";
// Check if we have already got the name for that type.
var names = fullName ? FullTypeNames : TypeNames;
string name;
if (names.TryGetValue(type, out name))
return name;
var text = new StringBuilder();
if (type.IsArray)// Array = TypeName[].
{
text.Append(type.GetElementType().GetNameCS(fullName));
text.Append('[');
var dimensions = type.GetArrayRank();
while (dimensions-- > 1)
text.Append(",");
text.Append(']');
goto Return;
}
if (type.IsPointer)// Pointer = TypeName*.
{
text.Append(type.GetElementType().GetNameCS(fullName));
text.Append('*');
goto Return;
}
if (type.IsGenericParameter)// Generic Parameter = TypeName (for unspecified generic parameters).
{
text.Append(type.Name);
goto Return;
}
var underlyingType = Nullable.GetUnderlyingType(type);
if (underlyingType != null)// Nullable = TypeName?.
{
text.Append(underlyingType.GetNameCS(fullName));
text.Append('?');
goto Return;
}
// Other Type = Namespace.NestedTypes.TypeName<GenericArguments>.
if (fullName && type.Namespace != null)// Namespace.
{
text.Append(type.Namespace);
text.Append('.');
}
var genericArguments = 0;
if (type.DeclaringType != null)// Account for Nested Types.
{
// Count the nesting level.
var nesting = 1;
var declaringType = type.DeclaringType;
while (declaringType.DeclaringType != null)
{
declaringType = declaringType.DeclaringType;
nesting++;
}
// Append the name of each outer type, starting from the outside.
while (nesting-- > 0)
{
// Walk out to the current nesting level.
// This avoids the need to make a list of types in the nest or to insert type names instead of appending them.
declaringType = type;
for (int i = nesting; i >= 0; i--)
declaringType = declaringType.DeclaringType;
// Nested Type Name.
genericArguments = AppendNameAndGenericArguments(text, declaringType, fullName, genericArguments);
text.Append('.');
}
}
// Type Name.
AppendNameAndGenericArguments(text, type, fullName, genericArguments);
Return:// Remember and return the name.
name = text.ToString();
names.Add(type, name);
return name;
}
/************************************************************************************************************************/
/// <summary>
/// Appends the generic arguments of `type` (after skipping the specified number).
/// </summary>
public static int AppendNameAndGenericArguments(StringBuilder text, Type type, bool fullName = true, int skipGenericArguments = 0)
{
text.Append(type.Name);
if (type.IsGenericType)
{
var backQuote = type.Name.IndexOf('`');
if (backQuote >= 0)
{
text.Length -= type.Name.Length - backQuote;
var genericArguments = type.GetGenericArguments();
if (skipGenericArguments < genericArguments.Length)
{
text.Append('<');
var firstArgument = genericArguments[skipGenericArguments];
skipGenericArguments++;
if (firstArgument.IsGenericParameter)
{
while (skipGenericArguments < genericArguments.Length)
{
text.Append(',');
skipGenericArguments++;
}
}
else
{
text.Append(firstArgument.GetNameCS(fullName));
while (skipGenericArguments < genericArguments.Length)
{
text.Append(", ");
text.Append(genericArguments[skipGenericArguments].GetNameCS(fullName));
skipGenericArguments++;
}
}
text.Append('>');
}
}
}
return skipGenericArguments;
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Member Names
/************************************************************************************************************************/
/// <summary>
/// Returns the full name of a `member` as it would appear in C# code.
/// <para></para>
/// For example, passing this method info in as its own parameter would return "<see cref="UltEventUtils"/>.GetNameCS".
/// <para></para>
/// Note that when `member` is a <see cref="Type"/>, this method calls <see cref="GetNameCS(Type, bool)"/> instead.
/// </summary>
public static string GetNameCS(this MemberInfo member, bool fullName = true)
{
if (member == null)
return "null";
var type = member as Type;
if (type != null)
return type.GetNameCS(fullName);
var text = new StringBuilder();
if (member.DeclaringType != null)
{
text.Append(member.DeclaringType.GetNameCS(fullName));
text.Append('.');
}
text.Append(member.Name);
return text.ToString();
}
/************************************************************************************************************************/
/// <summary>
/// Appends the full name of a `member` as it would appear in C# code.
/// <para></para>
/// For example, passing this method info in as its own parameter would append "<see cref="UltEventUtils"/>.AppendName".
/// <para></para>
/// Note that when `member` is a <see cref="Type"/>, this method calls <see cref="GetNameCS(Type, bool)"/> instead.
/// </summary>
public static StringBuilder AppendNameCS(this StringBuilder text, MemberInfo member, bool fullName = true)
{
if (member == null)
{
text.Append("null");
return text;
}
var type = member as Type;
if (type != null)
{
text.Append(type.GetNameCS(fullName));
return text;
}
if (member.DeclaringType != null)
{
text.Append(member.DeclaringType.GetNameCS(fullName));
text.Append('.');
}
text.Append(member.Name);
return text;
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Deep to String
/************************************************************************************************************************/
/// <summary>Returns a string containing the value of each element in `collection`.</summary>
public static string DeepToString(this IEnumerable collection, string separator)
{
if (collection == null)
return "null";
else
return collection.GetEnumerator().DeepToString(separator);
}
/// <summary>Returns a string containing the value of each element in `collection` (each on a new line).</summary>
public static string DeepToString(this IEnumerable collection)
{
return collection.DeepToString(Environment.NewLine);
}
/************************************************************************************************************************/
/// <summary>Each element returned by `enumerator` is appended to `text`.</summary>
public static void AppendDeepToString(StringBuilder text, IEnumerator enumerator, string separator)
{
text.Append("[]");
var countIndex = text.Length - 1;
var count = 0;
if (enumerator != null)
{
while (enumerator.MoveNext())
{
text.Append(separator);
text.Append('[');
text.Append(count);
text.Append("] = ");
text.Append(enumerator.Current);
count++;
}
}
text.Insert(countIndex, count);
}
/// <summary>Returns a string containing the value of each element in `enumerator`.</summary>
public static string DeepToString(this IEnumerator enumerator, string separator)
{
var text = new StringBuilder();
AppendDeepToString(text, enumerator, separator);
return text.ToString();
}
/// <summary>Returns a string containing the value of each element in `enumerator` (each on a new line).</summary>
public static string DeepToString(this IEnumerator enumerator)
{
return enumerator.DeepToString(Environment.NewLine);
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
/// <summary>Commonly used <see cref="BindingFlags"/>.</summary>
public const BindingFlags
AnyAccessBindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
/************************************************************************************************************************/
/************************************************************************************************************************/
/// <summary>Commonly used <see cref="BindingFlags"/>.</summary>
public const BindingFlags
InstanceBindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
/************************************************************************************************************************/
/************************************************************************************************************************/
/// <summary>Commonly used <see cref="BindingFlags"/>.</summary>
public const BindingFlags
StaticBindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
/************************************************************************************************************************/
/// <summary>
/// Returns the <see cref="MemberInfo.DeclaringType"/> for constructors or <see cref="MethodInfo.ReturnType"/>
/// for regular methods.
/// </summary>
public static Type GetReturnType(this MethodBase method)
{
return method.IsConstructor ? method.DeclaringType : (method as MethodInfo).ReturnType;
}
/************************************************************************************************************************/
/// <summary>Returns "AssemblyQualifiedName.MethodName".</summary>
public static string GetFullyQualifiedName(MethodBase method)
{
return method.DeclaringType.AssemblyQualifiedName + "." + method.Name;
}
/************************************************************************************************************************/
/// <summary>
/// Calculate the number of removals, inserts, and replacements needed to turn `a` into `b`.
/// </summary>
public static int CalculateLevenshteinDistance(string a, string b)
{
if (string.IsNullOrEmpty(a))
{
return string.IsNullOrEmpty(b) ? 0 : b.Length;
}
if (string.IsNullOrEmpty(b))
return a.Length;
var n = a.Length;
var m = b.Length;
var d = new int[n + 1, m + 1];
// initialise the top and right of the table to 0, 1, 2, ...
for (int i = 0; i <= n; d[i, 0] = i++)
{
// Execution is contained in the For statement.
}
for (int j = 0; j <= m; d[0, j] = j++)
{
// Execution is contained in the For statement.
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
var cost = (b[j - 1] == a[i - 1]) ? 0 : 1;
d[i, j] = Mathf.Min(
Mathf.Min(d[i - 1, j] + 1, d[i, j - 1] + 1),
d[i - 1, j - 1] + cost);
}
}
return d[n, m];
}
/************************************************************************************************************************/
/// <summary>
/// Sorts `list`, maintaining the order of any elements with an identical comparison
/// (unlike the standard <see cref="List{T}.Sort(Comparison{T})"/> method).
/// </summary>
public static void StableInsertionSort<T>(IList<T> list, Comparison<T> comparison)
{
var count = list.Count;
for (int j = 1; j < count; j++)
{
var key = list[j];
var i = j - 1;
for (; i >= 0 && comparison(list[i], key) > 0; i--)
{
list[i + 1] = list[i];
}
list[i + 1] = key;
}
}
/// <summary>
/// Sorts `list`, maintaining the order of any elements with an identical comparison
/// (unlike the standard <see cref="List{T}.Sort()"/> method).
/// </summary>
public static void StableInsertionSort<T>(IList<T> list) where T : IComparable<T>
{
StableInsertionSort(list, (a, b) => a.CompareTo(b));
}
/************************************************************************************************************************/
/// <summary>
/// Translates a zero based index to a placement name: 0 = "1st", 1 = "2nd", etc.
/// </summary>
public static string GetPlacementName(int index)
{
switch (index)
{
case 0: return "1st";
case 1: return "2nd";
case 2: return "3rd";
default: return index + "th";
}
}
/************************************************************************************************************************/
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 53baa194aa4fb3a4abdd09b83c4d3b79
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,90 @@
// UltEvents // Copyright 2021 Kybernetik //
using UnityEngine;
namespace UltEvents
{
/// <summary>
/// Holds <see cref="UltEvent"/>s which are called by various <see cref="MonoBehaviour"/> update events:
/// <see cref="Update"/>, <see cref="LateUpdate"/>, and <see cref="FixedUpdate"/>.
/// </summary>
[AddComponentMenu(UltEventUtils.ComponentMenuPrefix + "Update Events")]
[HelpURL(UltEventUtils.APIDocumentationURL + "/UpdateEvents")]
[DisallowMultipleComponent]
public class UpdateEvents : MonoBehaviour
{
/************************************************************************************************************************/
[SerializeField]
private UltEvent _UpdateEvent;
/// <summary>Invoked by <see cref="Update"/>.</summary>
public UltEvent UpdateEvent
{
get
{
if (_UpdateEvent == null)
_UpdateEvent = new UltEvent();
return _UpdateEvent;
}
set { _UpdateEvent = value; }
}
/// <summary>Invokes <see cref="UpdateEvent"/>.</summary>
public virtual void Update()
{
if (_UpdateEvent != null)
_UpdateEvent.Invoke();
}
/************************************************************************************************************************/
[SerializeField]
private UltEvent _LateUpdateEvent;
/// <summary>Invoked by <see cref="LateUpdate"/>.</summary>
public UltEvent LateUpdateEvent
{
get
{
if (_LateUpdateEvent == null)
_LateUpdateEvent = new UltEvent();
return _LateUpdateEvent;
}
set { _LateUpdateEvent = value; }
}
/// <summary>Invokes <see cref="LateUpdateEvent"/>.</summary>
public virtual void LateUpdate()
{
if (_LateUpdateEvent != null)
_LateUpdateEvent.Invoke();
}
/************************************************************************************************************************/
[SerializeField]
private UltEvent _FixedUpdateEvent;
/// <summary>Invoked by <see cref="FixedUpdate"/>.</summary>
public UltEvent FixedUpdateEvent
{
get
{
if (_FixedUpdateEvent == null)
_FixedUpdateEvent = new UltEvent();
return _FixedUpdateEvent;
}
set { _FixedUpdateEvent = value; }
}
/// <summary>Invokes <see cref="FixedUpdateEvent"/>.</summary>
public virtual void FixedUpdate()
{
if (_FixedUpdateEvent != null)
_FixedUpdateEvent.Invoke();
}
/************************************************************************************************************************/
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: faa4e296e5a9d63458b22f3715d61570
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: