// UltEvents // Copyright 2020 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