// UltEvents // Copyright 2020 Kybernetik // #if UNITY_EDITOR using System; using System.Collections.Generic; using System.Reflection; using UnityEditor; namespace UltEvents.Editor { /// [Editor-Only] /// Manages the GUI state used when drawing events. /// internal sealed class DrawerState { /************************************************************************************************************************/ /// The currently active state. public static readonly DrawerState Current = new DrawerState(); /************************************************************************************************************************/ /// The for the event currently being drawn. public SerializedProperty EventProperty { get; private set; } /// The event currently being drawn. public UltEventBase Event { get; private set; } /************************************************************************************************************************/ /// The for the call currently being drawn. public SerializedProperty CallProperty { get; private set; } /// The for the target of the call currently being drawn. public SerializedProperty TargetProperty { get; private set; } /// The for the method name of the call currently being drawn. public SerializedProperty MethodNameProperty { get; private set; } /// The for the persistent arguments array of the call currently being drawn. public SerializedProperty PersistentArgumentsProperty { get; private set; } /// The index of the call currently being drawn. public int callIndex = -1; /// The call currently being drawn. public PersistentCall call; /// The parameters of the call currently being drawn. public ParameterInfo[] callParameters; /// The index of the parameter currently being drawn. public int parameterIndex; /************************************************************************************************************************/ /// If true, each call will be stored so that subsequent calls can link to their return value. public bool CachePreviousCalls { get; private set; } /// The calls of the current event that come before the current call currently being drawn. private readonly List PreviousCalls = new List(); /// The methods targeted by the calls of the event currently being drawn. private readonly List PersistentMethodCache = new List(); /************************************************************************************************************************/ /// The parameter currently being drawn. public ParameterInfo CurrentParameter { get { return callParameters[parameterIndex]; } } /************************************************************************************************************************/ /// Caches the event from the specified property and returns true as long as it is not null. public bool TryBeginEvent(SerializedProperty eventProperty) { Event = eventProperty.GetValue(); if (Event == null) return false; EventProperty = eventProperty; return true; } /// Cancels out a call to . public void EndEvent() { EventProperty = null; Event = null; } /************************************************************************************************************************/ /// Starts caching calls so that subsequent calls can link to earlier return values. public void BeginCache() { CacheLinkedArguments(); CachePreviousCalls = true; } /// Cancels out a call to . public void EndCache() { CachePreviousCalls = false; PreviousCalls.Clear(); } /************************************************************************************************************************/ /// Caches the call from the specified property. public void BeginCall(SerializedProperty callProperty) { CallProperty = callProperty; TargetProperty = GetTargetProperty(callProperty); MethodNameProperty = GetMethodNameProperty(callProperty); PersistentArgumentsProperty = GetPersistentArgumentsProperty(callProperty); call = GetCall(callProperty); } /// Cancels out a call to . public void EndCall() { if (CachePreviousCalls) PreviousCalls.Add(call); call = null; } /************************************************************************************************************************/ /// Returns the property encapsulating the . public static SerializedProperty GetTargetProperty(SerializedProperty callProperty) { return callProperty.FindPropertyRelative(Names.PersistentCall.Target); } /// Returns the property encapsulating the . public static SerializedProperty GetMethodNameProperty(SerializedProperty callProperty) { return callProperty.FindPropertyRelative(Names.PersistentCall.MethodName); } /// Returns the property encapsulating the . public static SerializedProperty GetPersistentArgumentsProperty(SerializedProperty callProperty) { return callProperty.FindPropertyRelative(Names.PersistentCall.PersistentArguments); } /// Returns the call encapsulated by the specified property. public static PersistentCall GetCall(SerializedProperty callProperty) { return callProperty.GetValue(); } /************************************************************************************************************************/ #region Linked Argument Cache /************************************************************************************************************************/ /// Stores all the persistent methods in the current event. 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); } } /************************************************************************************************************************/ /// Ensures that any linked parameters remain linked to the correct target index. 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(); } /************************************************************************************************************************/ /// Returns the index of the persistent call that targets the specified `method` or -1 if there is none. 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; } /************************************************************************************************************************/ /// Returns the method cached from the persistent call at the specified `index`. public MethodBase GetLinkedMethod(int index) { if (index >= 0 && index < PersistentMethodCache.Count) return PersistentMethodCache[index]; else return null; } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Previous Call Cache /************************************************************************************************************************/ /// Tries to get the details of the a parameter or return value of the specified `type`. 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; } /************************************************************************************************************************/ /// Tries to get the details of the a parameter or return value of the current parameter type. 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; } } /************************************************************************************************************************/ /// The number of persistent calls that came earlier in the current event. public int PreviousCallCount { get { return PreviousCalls.Count; } } /************************************************************************************************************************/ /// Returns the persistent call at the specified index in the current event. public PersistentCall GetPreviousCall(int index) { if (index >= 0 && index < PreviousCalls.Count) return PreviousCalls[index]; else return null; } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ /// Copies the contents of the `other` state to overwrite this one. 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); } /************************************************************************************************************************/ /// Clears all the details of this state. 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