Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

UltEvents - The Ultimate Event System For The Ultimate Price - FREE

Discussion in 'Assets and Asset Store' started by Kybernetik, Jun 1, 2019.

  1. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
    I'm not seeing any errors from a simple test. What does your code look like?
     
  2. Flavelius

    Flavelius

    Joined:
    Jul 8, 2012
    Posts:
    909
    This is how it should look:
    upload_2021-10-30_22-7-25.png
    (and how it looks on one object)
    And this is how it looks on the other object:
    upload_2021-10-30_22-8-28.png
    (and here it print-spams the error message)

    The code is like this:

    //This is inside a monobehaviour
    Code (CSharp):
    1. [ShowInInspector, ListDrawerSettings(ShowIndexLabels = false, ListElementLabelName = "AbilityName", CustomAddFunction = nameof(ShowAddPopup)), HideReferenceObjectPicker]
    2. [SerializeReference] List<MachineAbilityPropertyAssociation> abilityProperties = new List<MachineAbilityPropertyAssociation>();
    3.  
    4. //This is the base of one of the classes that is instanced inside each of the 'MachineAbilityPropertyAssociation's in the list above (which are specialized subclasses, ie. FloatMachineAbility..., BoolMachineAbility... etc with relating ExposedComponentPropery-subclasses), but only this one errors out
    5. public abstract class EnumExposedComponentProperty<T> : ExposedComponentProperty<Enum>, IExposedComponentProperty<T> where T : Enum
    6. {
    7. [...]
    8.     [SerializeField] EnumEvent onValueChanged = new EnumEvent();
    9.  
    10.     [SerializeField] UltEvent<T> onEnumValueChanged = new UltEvent<T>();
    11. [...]
    12. }
    13.  
    14. //This is the inherited class that specifies the generic argument to make it unity-serializable
    15. [Serializable]
    16. public class ToggleDirectionsExposedComponentProperty: EnumExposedComponentProperty<ToggleDirections> {}
    17.  
    18. //This is the subclass using it
    19. [Serializable]
    20. public class ToggleDirectionsPropertyAssociation: MachineAbilityPropertyAssociation<ToggleDirections>
    21. {
    22. [...]
    23.    [SerializeField] ToggleDirectionsExposedComponentProperty property = new ToggleDirectionsExposedComponentProperty();
    24.  
    25.    public override IExposedComponentProperty<ToggleDirections> ValueProperty => property;
    26. [...]
    27. }
    28.  
    The strange thing is that both objects from the screenshots use the same code/classes and just one errors out; which makes me believe it has something to do with the serialized data.
     
    Last edited: Oct 30, 2021
  3. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
    I tried fiddling with that a bit to get rid of the compile errors and I'm not getting any errors from the Inspector. Can you try making a minimal reproduction project?
     
  4. MUGIK

    MUGIK

    Joined:
    Jul 2, 2015
    Posts:
    429
  5. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
    The method selection menu is something I'd definitely like to improve, but unfortunately since it's a free asset that already does what I need it to it's hard to justify investing more time developing UltEvents instead of working on my other projects.
     
    MUGIK likes this.
  6. imgodot

    imgodot

    Joined:
    Nov 29, 2013
    Posts:
    212
    Background: I am using Ultimate Events. I have a singleton attached to a scene object and the singleton has all my events declared in it.

    I have one event that triggers a method in a script on a prefab.
    When playing, if I invoke the event, the method on the non-instantiated prefab is executed.
    That is odd but, okay, Unity treats a prefab like a special type of GameObject.

    However, I only want the method on the prefab to be triggered AFTER the prefab is instantiated.
    So, I disable the script on the prefab thinking that the method won't be triggered by the event.
    When playing, if I invoke the event, the method on the non-instantiated prefab is STILL executed.

    I don't know if this is a Unity thing or not but, is there some way to prevent a method on a prefab from being executed by an event when the prefab hasn't been instantiated.
     
  7. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
    Disabling a component doesn't prevent methods from being called on it, all it does is make Unity stop calling its methods like Update.

    You could make a static method which does nothing if the singleton isn't instantiated and have the event call that.
     
  8. imgodot

    imgodot

    Joined:
    Nov 29, 2013
    Posts:
    212
    Kyb,

    I solved it a different way, but have another question.

    Can I have a list that can contain any type of event?

    Currently, I have 3 types of events defined separately.
    Code (CSharp):
    1. public UltEvents.UltEvent PDEventVOID;
    2. public UltEvents.UltEvent<int> PDEventINT;
    3. public PDEventWithParam _PDTestEventWithEnumParam;
    4.  
    In special instances, I need to loop through all the targets of all the events; so I have to hard-code a loop through each of the three event types.
    If I had one list I could loop through, it would make for more maintainable code.
    I'm not sure it that is possible or how to properly define the list.

    Thanks.
    -- Paul
     
  9. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
    All event types implement IUltEventBase so if you make a List<IUltEventBase> you'll be able to add any event to it and access its PersistentCalls.
     
    hopeful likes this.
  10. imgodot

    imgodot

    Joined:
    Nov 29, 2013
    Posts:
    212
    Thanks. You were right, UltEventBase did it.

    Now I'm struggling with trying to remove a persistent call.
    The code executes without an issue, but the caller remains, stubbornly.
    I know I'm doing something obviously wrong, but I don't know what it is.
    The relevant code is below.

    Thanks.
    -- Paul

    ================ CODE ===============================
    I have an event defined and declared like this:
    Code (CSharp):
    1. [System.Serializable]
    2. public sealed class PDEvent_INTParam : UltEvents.UltEvent<int> { }
    3.  
    4. public PDEvent_INTParam PDEventINT;
    5.  
    I call a method to remove a persistent call from the event like this:
    Code (CSharp):
    1. public void RemoveCallToMethod(System.Action<int> argMethod) {
    2.                 PDEventINT.PersistentCalls -= argMethod;
    3. }
     
  11. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
    PersistentCalls is a List<PersistentCall> so you can't use the -= operator on it and it couldn't contain an Action<int> anyway.

    If the argMethod was added to the event's DynamicCalls, then you'll have to remove it from the DynamicCalls.

    Or if it's a persistent call, you'll need to iterate through all the PersistentCalls to find one which calls that method.
     
  12. imgodot

    imgodot

    Joined:
    Nov 29, 2013
    Posts:
    212
    I am still having trouble removing a persistent call.

    When I loop through the PersistentCallsList on the event, I see various methods and properties of the "call", but I don't see how to remove the "call" from the PersistentCallsList.

    Thanks.
    -- Paul

    Code (CSharp):
    1.         foreach (var call in PDEventINT.PersistentCallsList) {
    2.             // call.Invoke
    3.             // call.Method
    4.             // call.Target
    5.             // call.PersistentArguments
    6.             // call.SetMethod
    7.         }
    8.  
     
  13. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
    Code (CSharp):
    1. if (argMethod.Method == call.Method)
    2. {
    3.     PDEventINT.PersistentCallsList.Remove(call)
    4.     break;
    5. }
     
  14. imgodot

    imgodot

    Joined:
    Nov 29, 2013
    Posts:
    212
    Thanks, that worked for a single, specific event.

    Step 2 is to do that for a list of different methods contained in a List<IUltEventBase> that suggested previously.
    Looping through the list, I can get the PersistentCallsList for each <IUltEventBase> in the list.
    However, one thing I can't do is add a DynamicCall to an item in the list.

    What kind of voodoo is necessary for me to add a DynamicCall to an event in the List<IUltEventBase>?

    Thanks.
    -- Paul
     
  15. imgodot

    imgodot

    Joined:
    Nov 29, 2013
    Posts:
    212
    Kyb,

    How tough do you think it would be to allow a persistent call to have its link property (the infinity symbol button in the inspector) settable programmatically at runtime?

    I would love to be able to update/repoint a persistent call at runtime and keep the automatic parameter passing capability.

    I've looked at the UltEvents source to try and see how I might arrange that, but my only fumbling test was making the persistent argument "_Type" public so I could set it to "Parameter" after assigning a method using call.SetMethod(<myMethod>). That did work to set it back to "Parameter" after SetMethod(), but the target reference in the inspector turned red and was obviously angry with me.

    I realize it may have been quite a while since you last looked at the code, but I'm just looking for an idea of what you think the difficulty would be.

    Thanks.
    -- Paul
     
  16. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
    Every type of UltEvent has a different DynamicCall type. UltEvent has Action and UltEvent<T> has Action<T> so you can't assign the same kind of delegates to them or call them with the same parameters.

    Take a look at the comments on PersistentArgumentType.ReturnValue which explain the other values that need to be stored. It needs to store the type of the linked parameter. If it corresponds to a basic PersistentArgumentType then that type is casted to a float so it can be stored in _X but otherwise its name needs to be stored in _String. I can't remember if there were any other issues that prevented me from exposing that functionality.
     
  17. imgodot

    imgodot

    Joined:
    Nov 29, 2013
    Posts:
    212
    Thanks for all the info.
    -- Paul
     
  18. imgodot

    imgodot

    Joined:
    Nov 29, 2013
    Posts:
    212
    I tried to force the PersistenArgument to be "linked" through code by setting the "Type" to "Parameter" and, in my case, setting "SystemType" to Int32. I tried adding code to the "public PersistentCall AddPersistentCall(Delegate method)" function in UltEventBase to assign "Type" and "SytemType", however, their property SETs prevented it from working; setting one triggered the other to be reset/cleared. So, I tried creating a method in the "PersistentArgument" class to assign values to the underlying "_Type" and "_SystemType" values.

    Everything seemed fine, but after my call, the PersistentArgument "_SystemType was reset/cleared.

    Do you have any thoughts on why the "_SystemType" might be getting reset?
    -- Paul

    Code (CSharp):
    1. UltEventBase:
    2.        public PersistentCall AddPersistentCall(Delegate method)
    3.         {
    4.             if (_PersistentCalls == null)
    5.                 _PersistentCalls = new List<PersistentCall>(4);
    6.  
    7.             var call = new PersistentCall(method);
    8.  
    9.              [COLOR=#ff0000]THESE CALLS FAILED.[/COLOR]
    10.             //call.PersistentArguments[0].Type = PersistentArgumentType.Parameter;
    11.             //call.PersistentArguments[0].SystemType = typeof(Int32);
    12.  
    13. [COLOR=#ff0000]             THE VALUES APPEARED CORRECT INSIDE THE "PD_SetPersistentStuff()" METHOD.[/COLOR]
    14.             call.PersistentArguments[0].PD_SetPersistentStuff(PersistentArgumentType.Parameter, typeof(Int32));
    15.  
    16. [COLOR=#ff0000]            BUT, WHEN I GET BACK HERE, THE PERSISTENTARGUMENT[0] HAS LOST ITS "_SystemType".[/COLOR]
    17.             _PersistentCalls.Add(call);
    18.             return call;
    19.         }
    Here is my method in the PersistenArgument class:
    Code (CSharp):
    1.        public void PD_SetPersistentStuff(PersistentArgumentType argType, Type argSystemType) {
    2.             _Type = argType;
    3.             [COLOR=#ff4d4d]I TRIED IT WITH AND WITHOUT THIS NEXT LINE, BUT STILL FAILED.
    4.              PLUS, "_SystemType" WAS ALREADY SET PROPERLY.[/COLOR]
    5.             //_SystemType = argSystemType;
    6.             _HasSystemType = true;
    7.         }
    8.  
     
  19. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
    _SystemType is a non-serialized field so it will naturally lose its value when scripts are recompiled, the scene is reloaded, etc.

    If you're checking the SystemType property, note that in the editor its getter always calls GetArgumentType to derive the type from the actual serialized fields.
     
  20. MUGIK

    MUGIK

    Joined:
    Jul 2, 2015
    Posts:
    429
    Gonna flex a bit :cool:

    Just made searchable method selection dropdown based on unity's AdvancedDropdown

    upload_2022-1-30_20-10-7.png


    upload_2022-1-30_20-10-27.png


    upload_2022-1-30_20-10-45.png

    All you need to do is to
    1. place provided script under 'Assets/Plugins/UltEvents/Inspector' folder
    2. modify PersistantCallDrawer.cs at line 390
      MethodSelectionMenu.ShowMenu(area);

      change into
      AdvancedMethodSelectionMenu.ShowMenu(area);
    3. [Optionally] disable all Display Options for Base Types
      upload_2022-1-30_20-23-37.png
    Note: didn't test it with static methods cuz I don't use them.

    Code (CSharp):
    1. // UltEvents // Extended by STARasGAMES
    2.  
    3. #if UNITY_EDITOR
    4.  
    5. using System;
    6. using System.Collections.Generic;
    7. using System.Linq;
    8. using System.Reflection;
    9. using System.Text;
    10. using UnityEditor;
    11. using UnityEditor.IMGUI.Controls;
    12. using UnityEngine;
    13. using Object = UnityEngine.Object;
    14.  
    15. namespace UltEvents.Editor
    16. {
    17.     /// <summary>[Editor-Only]
    18.     /// Manages the construction of menus for selecting methods for <see cref="PersistentCall"/>s.
    19.     /// </summary>
    20.     internal static class AdvancedMethodSelectionMenu
    21.     {
    22.  
    23.         /************************************************************************************************************************/
    24.  
    25.         #region Fields
    26.  
    27.         /************************************************************************************************************************/
    28.  
    29.         /// <summary>
    30.         /// The drawer state from when the menu was opened which needs to be restored when a method is selected because
    31.         /// menu items are executed after the frame finishes and the drawer state is cleared.
    32.         /// </summary>
    33.         private static readonly DrawerState
    34.             CachedState = new DrawerState();
    35.  
    36.         private static readonly AdvancedDropdownState
    37.             CachedDropdownState = new AdvancedDropdownState();
    38.  
    39.         private static readonly StringBuilder
    40.             LabelBuilder = new StringBuilder();
    41.  
    42.         // These fields should really be passed around as parameters, but they make all the method signatures annoyingly long.
    43.         private static MethodBase _CurrentMethod;
    44.         private static BindingFlags _Bindings;
    45.         private static MethodSelectionDropdown _Menu;
    46.  
    47.         /************************************************************************************************************************/
    48.  
    49.         #endregion
    50.  
    51.         /************************************************************************************************************************/
    52.  
    53.         #region Entry Point
    54.  
    55.         /************************************************************************************************************************/
    56.      
    57.         /// <summary>Opens the menu near the specified `area`.</summary>
    58.         public static void ShowMenu(Rect area)
    59.         {
    60.             CachedState.CopyFrom(DrawerState.Current);
    61.  
    62.             _CurrentMethod = CachedState.call.GetMethodSafe();
    63.             _Bindings = GetBindingFlags();
    64.             _Menu = new MethodSelectionDropdown(CachedDropdownState);
    65.  
    66.             // BoolPref.AddDisplayOptions(_Menu);
    67.             AddDisplayOptions(_Menu);
    68.  
    69.             Object[] targetObjects;
    70.             var targets = GetObjectReferences(CachedState.TargetProperty, out targetObjects);
    71.  
    72.             AddCoreItems(targets);
    73.  
    74.             // Populate the main contents of the menu.
    75.             {
    76.                 if (targets == null)
    77.                 {
    78.                     var serializedMethodName = CachedState.MethodNameProperty.stringValue;
    79.                     Type declaringType;
    80.                     string methodName;
    81.                     PersistentCall.GetMethodDetails(serializedMethodName, null, out declaringType, out methodName);
    82.  
    83.                     // If we have no target, but do have a type, populate the menu with that type's statics.
    84.                     if (declaringType != null)
    85.                     {
    86.                         PopulateMenuWithStatics(targetObjects, declaringType);
    87.  
    88.                         goto ShowMenu;
    89.                     }
    90.                     else // If we have no type either, pretend the inspected objects are the targets.
    91.                     {
    92.                         targets = targetObjects;
    93.                     }
    94.                 }
    95.  
    96.                 // Ensure that all targets share the same type.
    97.                 var firstTarget = ValidateTargetsAndGetFirst(targets);
    98.                 if (firstTarget == null)
    99.                 {
    100.                     targets = targetObjects;
    101.                     firstTarget = targets[0];
    102.                 }
    103.  
    104.                 // Add menu items according to the type of the target.
    105.                 if (firstTarget is GameObject)
    106.                     PopulateMenuForGameObject("", false, targets);
    107.                 else if (firstTarget is Component)
    108.                     PopulateMenuForComponent(targets);
    109.                 else
    110.                     PopulateMenuForObject(targets);
    111.             }
    112.  
    113.             ShowMenu:
    114.  
    115.             _Menu.Show(area);
    116.             area.x -= 150;
    117.             area.width += 150;
    118.             SetHeightForOpenedPopup(area, 500f);
    119.  
    120.             GC.Collect();
    121.         }
    122.  
    123.         private static void SetHeightForOpenedPopup(Rect rect, float height)
    124.         {
    125.             var window = EditorWindow.focusedWindow;
    126.             if(window == null)
    127.             {
    128.                 Debug.LogWarning("EditorWindow.focusedWindow was null.");
    129.                 return;
    130.             }
    131.  
    132.             if(!string.Equals(window.GetType().FullName, "UnityEditor.IMGUI.Controls.AdvancedDropdownWindow"))
    133.             {
    134.                 Debug.LogWarning("EditorWindow.focusedWindow " + EditorWindow.focusedWindow.GetType().FullName + " was not in expected namespace.");
    135.                 return;
    136.             }
    137.             var position = window.position;
    138.             position.height = height;
    139.             position.width = rect.width;
    140.             window.minSize = position.size;
    141.             window.maxSize = position.size;
    142.             window.position = position;
    143.             window.ShowAsDropDown(GUIUtility.GUIToScreenRect(rect), position.size);
    144.         }
    145.      
    146.         /************************************************************************************************************************/
    147.  
    148.         private static BindingFlags GetBindingFlags()
    149.         {
    150.             var bindings = BindingFlags.Public | BindingFlags.Instance;
    151.  
    152.             if (BoolPref.ShowNonPublicMethods)
    153.                 bindings |= BindingFlags.NonPublic;
    154.  
    155.             if (BoolPref.ShowStaticMethods)
    156.                 bindings |= BindingFlags.Static;
    157.  
    158.             return bindings;
    159.         }
    160.  
    161.         /************************************************************************************************************************/
    162.  
    163.         private static void AddCoreItems(Object[] targets)
    164.         {
    165.             _Menu.AddItem(new GUIContent("Null"), _CurrentMethod == null, () =>
    166.             {
    167.                 DrawerState.Current.CopyFrom(CachedState);
    168.  
    169.                 if (targets != null)
    170.                 {
    171.                     PersistentCallDrawer.SetMethod(null);
    172.                 }
    173.                 else
    174.                 {
    175.                     // For a static method, remove the method name but keep the declaring type.
    176.                     var methodName = CachedState.MethodNameProperty.stringValue;
    177.                     var lastDot = methodName.LastIndexOf('.');
    178.                     if (lastDot < 0)
    179.                         CachedState.MethodNameProperty.stringValue = null;
    180.                     else
    181.                         CachedState.MethodNameProperty.stringValue = methodName.Substring(0, lastDot + 1);
    182.  
    183.                     CachedState.PersistentArgumentsProperty.arraySize = 0;
    184.  
    185.                     CachedState.MethodNameProperty.serializedObject.ApplyModifiedProperties();
    186.                 }
    187.  
    188.                 DrawerState.Current.Clear();
    189.             });
    190.  
    191.             var isStatic = _CurrentMethod != null && _CurrentMethod.IsStatic;
    192.             if (targets != null && !isStatic)
    193.             {
    194.                 _Menu.AddItem(new GUIContent("Static Method"), isStatic, () =>
    195.                 {
    196.                     DrawerState.Current.CopyFrom(CachedState);
    197.  
    198.                     PersistentCallDrawer.SetTarget(null);
    199.  
    200.                     DrawerState.Current.Clear();
    201.                 });
    202.             }
    203.  
    204.             _Menu.AddSeparator("");
    205.         }
    206.  
    207.         /************************************************************************************************************************/
    208.  
    209.         private static Object[] GetObjectReferences(SerializedProperty property, out Object[] targetObjects)
    210.         {
    211.             targetObjects = property.serializedObject.targetObjects;
    212.  
    213.             if (property.hasMultipleDifferentValues)
    214.             {
    215.                 var references = new Object[targetObjects.Length];
    216.                 for (int i = 0; i < references.Length; i++)
    217.                 {
    218.                     using (var serializedObject = new SerializedObject(targetObjects[i]))
    219.                     {
    220.                         references[i] = serializedObject.FindProperty(property.propertyPath).objectReferenceValue;
    221.                     }
    222.                 }
    223.  
    224.                 return references;
    225.             }
    226.             else
    227.             {
    228.                 var target = property.objectReferenceValue;
    229.                 if (target != null)
    230.                     return new Object[] {target};
    231.                 else
    232.                     return null;
    233.             }
    234.         }
    235.  
    236.         /************************************************************************************************************************/
    237.  
    238.         private static Object ValidateTargetsAndGetFirst(Object[] targets)
    239.         {
    240.             var firstTarget = targets[0];
    241.             if (firstTarget == null)
    242.                 return null;
    243.  
    244.             var targetType = firstTarget.GetType();
    245.  
    246.             // Make sure all targets have the exact same type.
    247.             // Unfortunately supporting inheritance would be more complicated.
    248.  
    249.             var i = 1;
    250.             for (; i < targets.Length; i++)
    251.             {
    252.                 var obj = targets[i];
    253.                 if (obj == null || obj.GetType() != targetType)
    254.                 {
    255.                     return null;
    256.                 }
    257.             }
    258.  
    259.             return firstTarget;
    260.         }
    261.  
    262.         /************************************************************************************************************************/
    263.  
    264.         private static T[] GetRelatedObjects<T>(Object[] objects, Func<Object, T> getRelatedObject)
    265.         {
    266.             var relatedObjects = new T[objects.Length];
    267.  
    268.             for (int i = 0; i < relatedObjects.Length; i++)
    269.             {
    270.                 relatedObjects[i] = getRelatedObject(objects[i]);
    271.             }
    272.  
    273.             return relatedObjects;
    274.         }
    275.  
    276.         /************************************************************************************************************************/
    277.  
    278.         #endregion
    279.  
    280.         /************************************************************************************************************************/
    281.  
    282.         #region Populate for Objects
    283.  
    284.         /************************************************************************************************************************/
    285.  
    286.         private static void PopulateMenuWithStatics(Object[] targets, Type type)
    287.         {
    288.             var firstTarget = targets[0];
    289.             var component = firstTarget as Component;
    290.             if (!ReferenceEquals(component, null))
    291.             {
    292.                 var gameObjects = GetRelatedObjects(targets, (target) => (target as Component).gameObject);
    293.                 PopulateMenuForGameObject("", true, gameObjects);
    294.             }
    295.             else
    296.             {
    297.                 PopulateMenuForObject(firstTarget.GetType().GetNameCS(BoolPref.ShowFullTypeNames) + " ->/", targets);
    298.             }
    299.  
    300.             _Menu.AddSeparator("");
    301.  
    302.             var bindings = BindingFlags.Static | BindingFlags.Public;
    303.             if (BoolPref.ShowNonPublicMethods)
    304.                 bindings |= BindingFlags.NonPublic;
    305.  
    306.             PopulateMenuWithMembers(type, bindings, "", null);
    307.         }
    308.  
    309.         /************************************************************************************************************************/
    310.  
    311.         private static void PopulateMenuForGameObject(string prefix, bool putGameObjectInSubMenu, Object[] targets)
    312.         {
    313.             var header = new GUIContent(prefix + "Selected GameObject and its Components");
    314.  
    315.             var gameObjectPrefix = prefix;
    316.             if (putGameObjectInSubMenu)
    317.             {
    318.                 _Menu.AddDisabledItem(header);
    319.                 gameObjectPrefix += "GameObject ->/";
    320.             }
    321.  
    322.             PopulateMenuForObject(gameObjectPrefix, targets);
    323.  
    324.             if (!putGameObjectInSubMenu)
    325.             {
    326.                 _Menu.AddSeparator(prefix);
    327.                 _Menu.AddDisabledItem(header);
    328.             }
    329.  
    330.             var gameObjects = GetRelatedObjects(targets, (target) => target as GameObject);
    331.             PopulateMenuForComponents(prefix, gameObjects);
    332.         }
    333.  
    334.         /************************************************************************************************************************/
    335.  
    336.         private static void PopulateMenuForComponents(string prefix, GameObject[] gameObjects)
    337.         {
    338.             var firstGameObject = gameObjects[0];
    339.             var components = firstGameObject.GetComponents<Component>();
    340.  
    341.             for (int i = 0; i < components.Length; i++)
    342.             {
    343.                 var component = components[i];
    344.  
    345.                 var targets = new Object[gameObjects.Length];
    346.                 targets[0] = component;
    347.  
    348.                 Type type;
    349.                 var typeIndex = GetComponentTypeIndex(component, components, out type);
    350.  
    351.                 int minTypeCount;
    352.                 Component unused;
    353.                 GetComponent(firstGameObject, type, typeIndex, out minTypeCount, out unused);
    354.  
    355.                 var j = 1;
    356.                 for (; j < gameObjects.Length; j++)
    357.                 {
    358.                     int typeCount;
    359.                     Component targetComponent;
    360.                     GetComponent(gameObjects[j], type, typeIndex, out typeCount, out targetComponent);
    361.                     if (typeCount <= typeIndex)
    362.                         goto NextComponent;
    363.  
    364.                     targets[j] = targetComponent;
    365.  
    366.                     if (minTypeCount > typeCount)
    367.                         minTypeCount = typeCount;
    368.                 }
    369.  
    370.                 var name = type.GetNameCS(BoolPref.ShowFullTypeNames) + " ->/";
    371.  
    372.                 if (minTypeCount > 1)
    373.                     name = UltEventUtils.GetPlacementName(typeIndex) + " " + name;
    374.  
    375.                 PopulateMenuForObject(prefix + name, targets);
    376.             }
    377.  
    378.             NextComponent: ;
    379.         }
    380.  
    381.         private static int GetComponentTypeIndex(Component component, Component[] components, out Type type)
    382.         {
    383.             type = component.GetType();
    384.  
    385.             var count = 0;
    386.  
    387.             for (int i = 0; i < components.Length; i++)
    388.             {
    389.                 var c = components[i];
    390.                 if (c == component)
    391.                     break;
    392.                 else if (c.GetType() == type)
    393.                     count++;
    394.             }
    395.  
    396.             return count;
    397.         }
    398.  
    399.         private static void GetComponent(GameObject gameObject, Type type, int targetIndex,
    400.             out int numberOfComponentsOfType, out Component targetComponent)
    401.         {
    402.             numberOfComponentsOfType = 0;
    403.             targetComponent = null;
    404.  
    405.             var components = gameObject.GetComponents(type);
    406.             for (int i = 0; i < components.Length; i++)
    407.             {
    408.                 var component = components[i];
    409.                 if (component.GetType() == type)
    410.                 {
    411.                     if (numberOfComponentsOfType == targetIndex)
    412.                         targetComponent = component;
    413.  
    414.                     numberOfComponentsOfType++;
    415.                 }
    416.             }
    417.         }
    418.  
    419.         /************************************************************************************************************************/
    420.  
    421.         private static void PopulateMenuForComponent(Object[] targets)
    422.         {
    423.             var gameObjects = GetRelatedObjects(targets, (target) => (target as Component).gameObject);
    424.  
    425.             PopulateMenuForGameObject("", true, gameObjects);
    426.             _Menu.AddSeparator("");
    427.  
    428.             // TODO: hides selected object members
    429.             // PopulateMenuForObject(targets);
    430.         }
    431.  
    432.         /************************************************************************************************************************/
    433.  
    434.         private static void PopulateMenuForObject(Object[] targets)
    435.         {
    436.             PopulateMenuForObject("", targets);
    437.         }
    438.  
    439.         private static void PopulateMenuForObject(string prefix, Object[] targets)
    440.         {
    441.             PopulateMenuWithMembers(targets[0].GetType(), _Bindings, prefix, targets);
    442.         }
    443.  
    444.         /************************************************************************************************************************/
    445.  
    446.         #endregion
    447.  
    448.         /************************************************************************************************************************/
    449.  
    450.         #region Populate for Types
    451.  
    452.         /************************************************************************************************************************/
    453.  
    454.         private static void PopulateMenuWithMembers(Type type, BindingFlags bindings, string prefix, Object[] targets)
    455.         {
    456.             var members = GetSortedMembers(type, bindings);
    457.             var previousDeclaringType = type;
    458.  
    459.             var firstSeparator = true;
    460.             var firstProperty = true;
    461.             var firstMethod = true;
    462.             var firstBaseType = true;
    463.             var nameMatchesNextMethod = false;
    464.  
    465.             var i = 0;
    466.             while (i < members.Count)
    467.             {
    468.                 ParameterInfo[] parameters;
    469.                 MethodInfo getter;
    470.                 var member = GetNextSupportedMember(members, ref i, out parameters, out getter);
    471.  
    472.                 GotMember:
    473.  
    474.                 if (member == null)
    475.                     return;
    476.  
    477.                 i++;
    478.  
    479.                 if (BoolPref.SubMenuForEachBaseType)
    480.                 {
    481.                     if (firstBaseType && member.DeclaringType != type)
    482.                     {
    483.                         if (firstSeparator)
    484.                             firstSeparator = false;
    485.                         else
    486.                             _Menu.AddSeparator(prefix);
    487.  
    488.                         var baseTypesOf = "Base Types of " + type.GetNameCS();
    489.                         if (BoolPref.SubMenuForBaseTypes)
    490.                         {
    491.                             prefix += baseTypesOf + " ->/";
    492.                         }
    493.                         else
    494.                         {
    495.                             _Menu.AddDisabledItem(new GUIContent(prefix + baseTypesOf));
    496.                         }
    497.  
    498.                         firstProperty = false;
    499.                         firstMethod = false;
    500.                         firstBaseType = false;
    501.                     }
    502.  
    503.                     if (previousDeclaringType != member.DeclaringType)
    504.                     {
    505.                         previousDeclaringType = member.DeclaringType;
    506.                         firstProperty = true;
    507.                         firstMethod = true;
    508.                         firstSeparator = true;
    509.                     }
    510.                 }
    511.  
    512.                 var property = member as PropertyInfo;
    513.                 if (property != null)
    514.                 {
    515.                     AppendGroupHeader(prefix, "Properties in ", member.DeclaringType, type, ref firstProperty,
    516.                         ref firstSeparator);
    517.  
    518.                     AddSelectPropertyItem(prefix, targets, type, property, getter);
    519.                     continue;
    520.                 }
    521.  
    522.                 var method = member as MethodBase;
    523.                 if (method != null)
    524.                 {
    525.                     AppendGroupHeader(prefix, "Methods in ", member.DeclaringType, type, ref firstMethod,
    526.                         ref firstSeparator);
    527.  
    528.                     // Check if the method name matched the previous or next method to group them.
    529.                     if (BoolPref.GroupMethodOverloads)
    530.                     {
    531.                         var nameMatchedPreviousMethod = nameMatchesNextMethod;
    532.  
    533.                         ParameterInfo[] nextParameters;
    534.                         MethodInfo nextGetter;
    535.                         var nextMember = GetNextSupportedMember(members, ref i, out nextParameters, out nextGetter);
    536.  
    537.                         nameMatchesNextMethod = nextMember != null && method.Name == nextMember.Name;
    538.  
    539.                         if (nameMatchedPreviousMethod || nameMatchesNextMethod)
    540.                         {
    541.                             AddSelectMethodItem(prefix, targets, type, true, method, parameters);
    542.  
    543.                             if (i < members.Count)
    544.                             {
    545.                                 member = nextMember;
    546.                                 parameters = nextParameters;
    547.                                 getter = nextGetter;
    548.                                 goto GotMember;
    549.                             }
    550.                             else
    551.                             {
    552.                                 return;
    553.                             }
    554.                         }
    555.                     }
    556.  
    557.                     // Otherwise just build the label normally.
    558.                     AddSelectMethodItem(prefix, targets, type, false, method, parameters);
    559.                 }
    560.             }
    561.         }
    562.  
    563.         /************************************************************************************************************************/
    564.  
    565.         private static void AppendGroupHeader(string prefix, string name, Type declaringType, Type currentType,
    566.             ref bool firstInGroup, ref bool firstSeparator)
    567.         {
    568.             if (firstInGroup)
    569.             {
    570.                 LabelBuilder.Length = 0;
    571.                 LabelBuilder.Append(prefix);
    572.  
    573.                 if (BoolPref.SubMenuForEachBaseType && declaringType != currentType)
    574.                     AppendDeclaringTypeSubMenu(LabelBuilder, declaringType, currentType);
    575.  
    576.                 if (firstSeparator)
    577.                     firstSeparator = false;
    578.                 else
    579.                     _Menu.AddSeparator(LabelBuilder.ToString());
    580.  
    581.                 LabelBuilder.Append(name);
    582.  
    583.                 if (BoolPref.SubMenuForEachBaseType)
    584.                     LabelBuilder.Append(declaringType.GetNameCS());
    585.                 else
    586.                     LabelBuilder.Append(currentType.GetNameCS());
    587.  
    588.                 _Menu.AddDisabledItem(new GUIContent(LabelBuilder.ToString()));
    589.                 firstInGroup = false;
    590.             }
    591.         }
    592.  
    593.         private static void AppendDeclaringTypeSubMenu(StringBuilder text, Type declaringType, Type currentType)
    594.         {
    595.             if (BoolPref.SubMenuForEachBaseType)
    596.             {
    597.                 if (BoolPref.SubMenuForRootBaseType || declaringType != currentType)
    598.                 {
    599.                     text.Append(declaringType.GetNameCS());
    600.                     text.Append(" ->/");
    601.                 }
    602.             }
    603.         }
    604.  
    605.         /************************************************************************************************************************/
    606.  
    607.         private static void AddSelectPropertyItem(string prefix, Object[] targets, Type currentType,
    608.             PropertyInfo property, MethodInfo getter)
    609.         {
    610.             var defaultMethod = getter;
    611.  
    612.             MethodInfo setter = null;
    613.             if (IsSupported(property.PropertyType))
    614.             {
    615.                 setter = property.GetSetMethod(true);
    616.                 if (setter != null)
    617.                     defaultMethod = setter;
    618.             }
    619.  
    620.             LabelBuilder.Length = 0;
    621.             LabelBuilder.Append(prefix);
    622.  
    623.             // Declaring Type.
    624.             AppendDeclaringTypeSubMenu(LabelBuilder, property.DeclaringType, currentType);
    625.  
    626.             // Non-Public Grouping.
    627.             if (BoolPref.GroupNonPublicMethods && !IsPublic(property))
    628.                 LabelBuilder.Append("Non-Public Properties ->/");
    629.  
    630.             // Property Type and Name.
    631.             LabelBuilder.Append(property.PropertyType.GetNameCS(BoolPref.ShowFullTypeNames));
    632.             LabelBuilder.Append(' ');
    633.             LabelBuilder.Append(property.Name);
    634.  
    635.             // Get and Set.
    636.             LabelBuilder.Append(" { ");
    637.             if (getter != null) LabelBuilder.Append("get; ");
    638.             if (setter != null) LabelBuilder.Append("set; ");
    639.             LabelBuilder.Append('}');
    640.  
    641.             var label = LabelBuilder.ToString();
    642.             AddSetCallItem(label, defaultMethod, targets);
    643.         }
    644.  
    645.         /************************************************************************************************************************/
    646.  
    647.         private static void AddSelectMethodItem(string prefix, Object[] targets, Type currentType,
    648.             bool methodNameSubMenu,
    649.             MethodBase method, ParameterInfo[] parameters)
    650.         {
    651.             LabelBuilder.Length = 0;
    652.             LabelBuilder.Append(prefix);
    653.  
    654.             // Declaring Type.
    655.             AppendDeclaringTypeSubMenu(LabelBuilder, method.DeclaringType, currentType);
    656.  
    657.             // Non-Public Grouping.
    658.             if (BoolPref.GroupNonPublicMethods && !IsPublic(method))
    659.                 LabelBuilder.Append("Non-Public Methods ->/");
    660.  
    661.             // Overload Grouping.
    662.             if (methodNameSubMenu)
    663.                 LabelBuilder.Append(method.Name).Append(" ->/");
    664.  
    665.             // Method Signature.
    666.             LabelBuilder.Append(GetMethodSignature(method, parameters, true));
    667.  
    668.             var label = LabelBuilder.ToString();
    669.  
    670.             AddSetCallItem(label, method, targets);
    671.         }
    672.  
    673.         /************************************************************************************************************************/
    674.  
    675.         private static void AddSetCallItem(string label, MethodBase method, Object[] targets)
    676.         {
    677.             _Menu.AddItem(
    678.                 new GUIContent(label),
    679.                 method == _CurrentMethod,
    680.                 () =>
    681.                 {
    682.                     DrawerState.Current.CopyFrom(CachedState);
    683.  
    684.                     var i = 0;
    685.                     CachedState.CallProperty.ModifyValues<PersistentCall>((call) =>
    686.                     {
    687.                         var target = targets != null ? targets[i % targets.Length] : null;
    688.                         call.SetMethod(method, target);
    689.                         i++;
    690.                     }, "Set Persistent Call");
    691.  
    692.                     DrawerState.Current.Clear();
    693.                 });
    694.         }
    695.  
    696.         /************************************************************************************************************************/
    697.  
    698.         #endregion
    699.  
    700.         /************************************************************************************************************************/
    701.  
    702.         #region Member Gathering
    703.  
    704.         /************************************************************************************************************************/
    705.  
    706.         private static readonly Dictionary<BindingFlags, Dictionary<Type, List<MemberInfo>>>
    707.             MemberCache = new Dictionary<BindingFlags, Dictionary<Type, List<MemberInfo>>>();
    708.  
    709.         internal static void ClearMemberCache()
    710.         {
    711.             MemberCache.Clear();
    712.         }
    713.  
    714.         /************************************************************************************************************************/
    715.  
    716.         private static List<MemberInfo> GetSortedMembers(Type type, BindingFlags bindings)
    717.         {
    718.             // Get the cache for the specified bindings.
    719.             Dictionary<Type, List<MemberInfo>> memberCache;
    720.             if (!MemberCache.TryGetValue(bindings, out memberCache))
    721.             {
    722.                 memberCache = new Dictionary<Type, List<MemberInfo>>();
    723.                 MemberCache.Add(bindings, memberCache);
    724.             }
    725.  
    726.             // If the members for the specified type aren't cached for those bindings, gather and sort them.
    727.             List<MemberInfo> members;
    728.             if (!memberCache.TryGetValue(type, out members))
    729.             {
    730.                 var properties = type.GetProperties(bindings);
    731.                 var methods = type.GetMethods(bindings);
    732.  
    733.                 // When gathering static members, also include instance constructors.
    734.                 var constructors = ((bindings & BindingFlags.Static) == BindingFlags.Static)
    735.                     ? type.GetConstructors((bindings & ~BindingFlags.Static) | BindingFlags.Instance)
    736.                     : null;
    737.  
    738.                 var capacity = properties.Length + methods.Length;
    739.                 if (constructors != null)
    740.                     capacity += constructors.Length;
    741.  
    742.                 members = new List<MemberInfo>(capacity);
    743.                 members.AddRange(properties);
    744.                 if (constructors != null)
    745.                     members.AddRange(constructors);
    746.                 members.AddRange(methods);
    747.  
    748.                 // If the bindings include static, add static members from each base type.
    749.                 if ((bindings & BindingFlags.Static) == BindingFlags.Static && type.BaseType != null)
    750.                 {
    751.                     members.AddRange(GetSortedMembers(type.BaseType, bindings & ~BindingFlags.Instance));
    752.                 }
    753.  
    754.                 UltEventUtils.StableInsertionSort(members, CompareMembers);
    755.  
    756.                 memberCache.Add(type, members);
    757.             }
    758.  
    759.             return members;
    760.         }
    761.  
    762.         /************************************************************************************************************************/
    763.  
    764.         private static int CompareMembers(MemberInfo a, MemberInfo b)
    765.         {
    766.             if (BoolPref.SubMenuForEachBaseType)
    767.             {
    768.                 var result = CompareChildBeforeBase(a.DeclaringType, b.DeclaringType);
    769.                 if (result != 0)
    770.                     return result;
    771.             }
    772.  
    773.             // Compare types (properties before methods).
    774.             if (a is PropertyInfo)
    775.             {
    776.                 if (!(b is PropertyInfo))
    777.                     return -1;
    778.             }
    779.             else
    780.             {
    781.                 if (b is PropertyInfo)
    782.                     return 1;
    783.             }
    784.  
    785.             // Non-Public Sub-Menu.
    786.             if (BoolPref.GroupNonPublicMethods)
    787.             {
    788.                 if (IsPublic(a))
    789.                 {
    790.                     if (!IsPublic(b))
    791.                         return -1;
    792.                 }
    793.                 else
    794.                 {
    795.                     if (IsPublic(b))
    796.                         return 1;
    797.                 }
    798.             }
    799.  
    800.             // Compare names.
    801.             return a.Name.CompareTo(b.Name);
    802.         }
    803.  
    804.         /************************************************************************************************************************/
    805.  
    806.         private static int CompareChildBeforeBase(Type a, Type b)
    807.         {
    808.             if (a == b)
    809.                 return 0;
    810.  
    811.             while (true)
    812.             {
    813.                 a = a.BaseType;
    814.  
    815.                 if (a == null)
    816.                     return 1;
    817.  
    818.                 if (a == b)
    819.                     return -1;
    820.             }
    821.         }
    822.  
    823.         /************************************************************************************************************************/
    824.  
    825.         private static readonly Dictionary<MemberInfo, bool>
    826.             MemberToIsPublic = new Dictionary<MemberInfo, bool>();
    827.  
    828.         private static bool IsPublic(MemberInfo member)
    829.         {
    830.             bool isPublic;
    831.             if (!MemberToIsPublic.TryGetValue(member, out isPublic))
    832.             {
    833.                 switch (member.MemberType)
    834.                 {
    835.                     case MemberTypes.Constructor:
    836.                     case MemberTypes.Method:
    837.                         isPublic = (member as MethodBase).IsPublic;
    838.                         break;
    839.  
    840.                     case MemberTypes.Property:
    841.                         isPublic =
    842.                             (member as PropertyInfo).GetGetMethod() != null ||
    843.                             (member as PropertyInfo).GetSetMethod() != null;
    844.                         break;
    845.  
    846.                     default:
    847.                         throw new ArgumentException("Unhandled member type", "member");
    848.                 }
    849.  
    850.                 MemberToIsPublic.Add(member, isPublic);
    851.             }
    852.  
    853.             return isPublic;
    854.         }
    855.  
    856.         /************************************************************************************************************************/
    857.  
    858.         #endregion
    859.  
    860.         /************************************************************************************************************************/
    861.  
    862.         #region Supported Checks
    863.  
    864.         /************************************************************************************************************************/
    865.  
    866.         private static bool IsSupported(MethodBase method, out ParameterInfo[] parameters)
    867.         {
    868.             if (method.IsGenericMethod ||
    869.                 (method.IsSpecialName && (!method.IsConstructor || method.IsStatic)) ||
    870.                 method.Name.Contains("<") ||
    871.                 method.IsDefined(typeof(ObsoleteAttribute), true))
    872.             {
    873.                 parameters = null;
    874.                 return false;
    875.             }
    876.  
    877.             // Most UnityEngine.Object types shouldn't be constructed directly.
    878.             if (method.IsConstructor)
    879.             {
    880.                 if (typeof(Component).IsAssignableFrom(method.DeclaringType) ||
    881.                     typeof(ScriptableObject).IsAssignableFrom(method.DeclaringType))
    882.                 {
    883.                     parameters = null;
    884.                     return false;
    885.                 }
    886.             }
    887.  
    888.             parameters = method.GetParameters();
    889.             if (!IsSupported(parameters))
    890.                 return false;
    891.  
    892.             return true;
    893.         }
    894.  
    895.         private static bool IsSupported(PropertyInfo property, out MethodInfo getter)
    896.         {
    897.             if (property.IsSpecialName ||
    898.                 property.IsDefined(typeof(ObsoleteAttribute), true)) // Obsolete.
    899.             {
    900.                 getter = null;
    901.                 return false;
    902.             }
    903.  
    904.             getter = property.GetGetMethod(true);
    905.             if (getter == null && !IsSupported(property.PropertyType))
    906.                 return false;
    907.  
    908.             return true;
    909.         }
    910.  
    911.         /************************************************************************************************************************/
    912.  
    913.         /// <summary>
    914.         /// Returns true if the specified `type` can be represented by a <see cref="PersistentArgument"/>.
    915.         /// </summary>
    916.         public static bool IsSupported(Type type)
    917.         {
    918.             if (PersistentCall.IsSupportedNative(type))
    919.             {
    920.                 return true;
    921.             }
    922.             else
    923.             {
    924.                 int linkIndex;
    925.                 PersistentArgumentType linkType;
    926.                 return DrawerState.Current.TryGetLinkable(type, out linkIndex, out linkType);
    927.             }
    928.         }
    929.  
    930.         /// <summary>
    931.         /// Returns true if the type of each of the `parameters` can be represented by a <see cref="PersistentArgument"/>.
    932.         /// </summary>
    933.         public static bool IsSupported(ParameterInfo[] parameters)
    934.         {
    935.             for (int i = 0; i < parameters.Length; i++)
    936.             {
    937.                 if (!IsSupported(parameters[i].ParameterType))
    938.                     return false;
    939.             }
    940.  
    941.             return true;
    942.         }
    943.  
    944.         /************************************************************************************************************************/
    945.  
    946.         private static MemberInfo GetNextSupportedMember(List<MemberInfo> members, ref int startIndex,
    947.             out ParameterInfo[] parameters, out MethodInfo getter)
    948.         {
    949.             while (startIndex < members.Count)
    950.             {
    951.                 var member = members[startIndex];
    952.                 var property = member as PropertyInfo;
    953.                 if (property != null && IsSupported(property, out getter))
    954.                 {
    955.                     parameters = null;
    956.                     return member;
    957.                 }
    958.  
    959.                 var method = member as MethodBase;
    960.                 if (method != null && IsSupported(method, out parameters))
    961.                 {
    962.                     getter = null;
    963.                     return member;
    964.                 }
    965.  
    966.                 startIndex++;
    967.             }
    968.  
    969.             parameters = null;
    970.             getter = null;
    971.             return null;
    972.         }
    973.  
    974.         /************************************************************************************************************************/
    975.  
    976.         #endregion
    977.  
    978.         /************************************************************************************************************************/
    979.  
    980.         #region Method Signatures
    981.  
    982.         /************************************************************************************************************************/
    983.  
    984.         private static readonly Dictionary<MethodBase, string>
    985.             MethodSignaturesWithParameters = new Dictionary<MethodBase, string>(),
    986.             MethodSignaturesWithoutParameters = new Dictionary<MethodBase, string>();
    987.  
    988.         private static readonly StringBuilder
    989.             MethodSignatureBuilder = new StringBuilder();
    990.  
    991.         /************************************************************************************************************************/
    992.  
    993.         public static string GetMethodSignature(MethodBase method, ParameterInfo[] parameters,
    994.             bool includeParameterNames)
    995.         {
    996.             if (method == null)
    997.                 return null;
    998.  
    999.             var signatureCache = includeParameterNames
    1000.                 ? MethodSignaturesWithParameters
    1001.                 : MethodSignaturesWithoutParameters;
    1002.  
    1003.             string signature;
    1004.             if (!signatureCache.TryGetValue(method, out signature))
    1005.             {
    1006.                 signature = BuildAndCacheSignature(method, parameters, includeParameterNames, signatureCache);
    1007.             }
    1008.  
    1009.             return signature;
    1010.         }
    1011.  
    1012.         public static string GetMethodSignature(MethodBase method, bool includeParameterNames)
    1013.         {
    1014.             if (method == null)
    1015.                 return null;
    1016.  
    1017.             var signatureCache = includeParameterNames
    1018.                 ? MethodSignaturesWithParameters
    1019.                 : MethodSignaturesWithoutParameters;
    1020.  
    1021.             string signature;
    1022.             if (!signatureCache.TryGetValue(method, out signature))
    1023.             {
    1024.                 signature = BuildAndCacheSignature(method, method.GetParameters(), includeParameterNames,
    1025.                     signatureCache);
    1026.             }
    1027.  
    1028.             return signature;
    1029.         }
    1030.  
    1031.         /************************************************************************************************************************/
    1032.  
    1033.         private static string BuildAndCacheSignature(MethodBase method, ParameterInfo[] parameters,
    1034.             bool includeParameterNames,
    1035.             Dictionary<MethodBase, string> signatureCache)
    1036.         {
    1037.             MethodSignatureBuilder.Length = 0;
    1038.  
    1039.             var returnType = method.GetReturnType();
    1040.             MethodSignatureBuilder.Append(returnType.GetNameCS(false));
    1041.             MethodSignatureBuilder.Append(' ');
    1042.  
    1043.             MethodSignatureBuilder.Append(method.Name);
    1044.  
    1045.             MethodSignatureBuilder.Append(" (");
    1046.             for (int i = 0; i < parameters.Length; i++)
    1047.             {
    1048.                 if (i > 0)
    1049.                     MethodSignatureBuilder.Append(", ");
    1050.  
    1051.                 var parameter = parameters[i];
    1052.  
    1053.                 MethodSignatureBuilder.Append(parameter.ParameterType.GetNameCS(false));
    1054.                 if (includeParameterNames)
    1055.                 {
    1056.                     MethodSignatureBuilder.Append(' ');
    1057.                     MethodSignatureBuilder.Append(parameter.Name);
    1058.                 }
    1059.             }
    1060.  
    1061.             MethodSignatureBuilder.Append(')');
    1062.  
    1063.             var signature = MethodSignatureBuilder.ToString();
    1064.             MethodSignatureBuilder.Length = 0;
    1065.             signatureCache.Add(method, signature);
    1066.  
    1067.             return signature;
    1068.         }
    1069.  
    1070.         /************************************************************************************************************************/
    1071.  
    1072.         #endregion
    1073.  
    1074.         /************************************************************************************************************************/
    1075.      
    1076.      
    1077.         private static void AddDisplayOptions(MethodSelectionDropdown dropdown)
    1078.         {
    1079.             AddToDropdown(BoolPref.UseIndentation);
    1080.             AddToDropdown(BoolPref.AutoOpenMenu);
    1081.             AddToDropdown(BoolPref.AutoHideFooter);
    1082.             AddToDropdown(BoolPref.ShowNonPublicMethods);
    1083.             AddToDropdown(BoolPref.GroupNonPublicMethods);
    1084.             AddToDropdown(BoolPref.ShowStaticMethods);
    1085.             AddToDropdown(BoolPref.ShowFullTypeNames);
    1086.             AddToDropdown(BoolPref.GroupMethodOverloads);
    1087.             AddToDropdown(BoolPref.SubMenuForEachBaseType);
    1088.             AddToDropdown(BoolPref.SubMenuForBaseTypes);
    1089.             AddToDropdown(BoolPref.SubMenuForRootBaseType);
    1090.          
    1091.          
    1092.             void AddToDropdown(BoolPref pref)
    1093.             {
    1094.                 dropdown.AddItem(new GUIContent("Display Options ->/" + pref.Label), pref.Value, () =>
    1095.                 {
    1096.                     pref.Value = !pref.Value;
    1097.                     if (pref.OnChanged != null)
    1098.                         pref.OnChanged();
    1099.                 });
    1100.             }
    1101.         }
    1102.      
    1103.         class MethodSelectionDropdown : AdvancedDropdown
    1104.         {
    1105.             private readonly AdvancedDropdownItem _root;
    1106.             private readonly IDictionary<AdvancedDropdownItem, GenericMenu.MenuFunction> _functions;
    1107.             private readonly Texture2D _selectedIcon;
    1108.  
    1109.             public MethodSelectionDropdown(AdvancedDropdownState state) : base(state)
    1110.             {
    1111.                 _root = new AdvancedDropdownItem("Search");
    1112.                 _functions = new Dictionary<AdvancedDropdownItem, GenericMenu.MenuFunction>();
    1113.                 //d_Valid, Installed, PlayButton On, d_Toggle Icon
    1114.                 _selectedIcon = EditorGUIUtility.IconContent("d_Toggle Icon").image as Texture2D;
    1115.             }
    1116.  
    1117.             protected override AdvancedDropdownItem BuildRoot()
    1118.             {
    1119.                 return _root;
    1120.             }
    1121.  
    1122.             public void AddItem(GUIContent content, bool on, GenericMenu.MenuFunction func)
    1123.             {
    1124.                 var path = "";
    1125.                 var name = content.text;
    1126.                 if (name.Contains("/"))
    1127.                 {
    1128.                     name = name.Replace("//", "/");
    1129.                     path = name.Remove(name.LastIndexOf('/'));
    1130.                     // name = name.Substring(name.LastIndexOf('/')+1);
    1131.                     name = name.Replace("/", " ");
    1132.                 }
    1133.  
    1134.                 var root = FindItem(path);
    1135.                 if (root == null)
    1136.                     return;
    1137.                 var item = new AdvancedDropdownItem(name);
    1138.                 item.enabled = true;
    1139.                 if (on)
    1140.                     item.icon = _selectedIcon;
    1141.                 root.AddChild(item);
    1142.                 _functions.Add(item, func);
    1143.             }
    1144.  
    1145.             public void AddDisabledItem(GUIContent content)
    1146.             {
    1147.                 var path = "";
    1148.                 var name = content.text;
    1149.                 if (name.Contains("/"))
    1150.                 {
    1151.                     name = name.Replace("//", "/");
    1152.                     path = name.Remove(name.LastIndexOf('/'));
    1153.                     name = name.Substring(name.LastIndexOf('/'));
    1154.                 }
    1155.  
    1156.                 var root = FindItem(path);
    1157.                 if (root == null)
    1158.                     return;
    1159.                 var item = new AdvancedDropdownItem(name);
    1160.                 item.enabled = false;
    1161.                 root.AddChild(item);
    1162.             }
    1163.  
    1164.             public void AddSeparator(string path)
    1165.             {
    1166.                 var item = FindItem(path);
    1167.                 if (item == null)
    1168.                     return;
    1169.                 item.AddSeparator();
    1170.             }
    1171.  
    1172.             private AdvancedDropdownItem FindItem(string path)
    1173.             {
    1174.                 var s = path.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
    1175.                 var item = _root;
    1176.                 foreach (var s1 in s)
    1177.                 {
    1178.                     var nextItem = item.children.FirstOrDefault(x => x.name == s1);
    1179.  
    1180.                     if (nextItem == null)
    1181.                     {
    1182.                         nextItem = new AdvancedDropdownItem(s1);
    1183.                         item.AddChild(nextItem);
    1184.                     }
    1185.  
    1186.                     item = nextItem;
    1187.                 }
    1188.  
    1189.                 return item;
    1190.             }
    1191.  
    1192.             protected override void ItemSelected(AdvancedDropdownItem item)
    1193.             {
    1194.                 Debug.Log($"Selected {item.name}");
    1195.                 _functions[item].Invoke();
    1196.             }
    1197.         }
    1198.     }
    1199. }
    1200.  
    1201. #endif
     
    PGJ, Flavelius, imgodot and 3 others like this.
  21. imgodot

    imgodot

    Joined:
    Nov 29, 2013
    Posts:
    212
    Kyber,

    Thanks for all your prior help.

    Another question (of course):
    If I have a persistent listener that points to a method in an instantiated prefab, will I get a memory leak if the prefab is destroyed but the persistent listener is left in place?

    Thanks,
    -- Paul

    In my testing after my prefab is destroyed:
    * If I trigger the event pointing to a method in the prefab, no error occurs, but I do get a Debug warning, "Attempted to Invoke a PersistentCall which couldn't find it's method...."
    * If I trigger the event pointing to a method in the prefab, the event in the inspector shows red and correctly says "No type selected".
    * My crude testing for memory leaks didn't show an issue but, I'm not certain my translation of C# testing techniques are accurately transferable to Unity.
     
    Last edited: Feb 5, 2022
  22. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
    Destroying an object doesn't free up its memory, it just flags it as "destroyed" in Unity so that if you check if the object == null it really checks if it's actually null or if it exists but is flagged as destroyed.

    After everything referencing the object is cleaned up (including any UltEvents that use it) then the object is no longer needed and can be garbage collected to free up its memory.
     
  23. imgodot

    imgodot

    Joined:
    Nov 29, 2013
    Posts:
    212
    Kyber,

    Thanks for the info.

    Let me be sure I am clear on what you're saying.
    Let's say I have a singleton event manager with a script that contains a definition for an UltEvent.
    In the Unity inspector, I add several listeners to the UltEvent.
    I run my application/game; and win in a stunning victory, I might add.
    When I close my application/game do I need to programmatically remove all the persistent listeners I added in the editor?

    -- Paul
     
  24. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
    When you close the application, all its memory will always get freed (and there's nothing you could do to prevent that even if you wanted to).
     
  25. imgodot

    imgodot

    Joined:
    Nov 29, 2013
    Posts:
    212
    Nothing I can do to prevent that, huh?
    Hold my beer ...
     
  26. MUGIK

    MUGIK

    Joined:
    Jul 2, 2015
    Posts:
    429
    If we are talking in the context of Unity and safe C# I highly doubt you can make memory leak.
     
  27. badfitz66

    badfitz66

    Joined:
    Nov 21, 2013
    Posts:
    6
    Does UltEvents support arrays as function argument types?
     
  28. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
  29. StarFluke

    StarFluke

    Joined:
    Jan 19, 2013
    Posts:
    39
    Hey Kybernetik! Awesome tool!

    In UltEventBase.cs CopyFrom() I changed line 407 to:
    public virtual void CopyFrom(UltEventBase target, bool add = false)

    This of course did not affect the regular CopyFrom calls.

    And I changed line 418 to:
    if(!add)_PersistentCalls.Clear();

    This gives me the option to concatenate events.

    Do you have a better solution?

    Thanks,
    Daniel
     
  30. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
    That's a fine way to do it, though it wouldn't be unreasonable to just remove the Clear so the caller can call it before CopyFrom if that's what they want.
     
    StarFluke likes this.
  31. wedgiebee

    wedgiebee

    Joined:
    Aug 9, 2016
    Posts:
    36
    Hi there, just downloaded UltEvents, it's got some really cool features! I have a few questions:

    1) Is there a way to use an event's return value in code? For example something like this:

    Code (CSharp):
    1.     [System.Serializable] public class ReturnIEnumerator : UltEvent<IEnumerator> { }
    2.  
    3.     public ReturnIEnumerator returnMe;
    4.  
    5.     public void DoThing()
    6.     {
    7.         IEnumerator i = returnMe.Invoke();
    8.     }
    9.  
    10.     // example of the kind of method I'd want to assign in the inspector
    11.     protected IEnumerator ReturnThing()
    12.     {
    13.         yield return new WaitForFixedUpdate();
    14.     }
    15.  
    That's how it works in this system, but that one doesn't show enums in the inspector and doesn't have all the other cool UltEvent features!

    * * *

    2) I have to make the inspector really wide for the UI to render properly, otherwise it kind of spills out of its containers. Is there an easy way for me to fix this? Here's a picture:

    Screen Shot 2022-05-28 at 11.19.10 AM.png
     

    Attached Files:

    Last edited: May 28, 2022
  32. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
    1) Not really. Giving events a return value is on my list of things to look at if I ever rewrite the system, but it would take quite a bit of work to get there from the current state.
    UltEvent<IEnumerator>
    is an event that takes an
    IEnumerator
    as a parameter so what you want would need a completely separate class that interprets one of its generic parameters as the return type.

    The closest you could currently get would be to have ReturnThing store the value you want to return in a field so DoThing can use it.

    2) The width of that bit is set by line 100 in PersistentCallDrawer.cs:
    area.xMax = EditorGUIUtility.labelWidth + 12;
    . Replacing that with
    area.width *= 0.3f;
    seems to do a good job.
     
    wedgiebee likes this.
  33. ipotonic

    ipotonic

    Joined:
    Mar 9, 2019
    Posts:
    7
    Hey Kybernetik! Thanks a lot from making UltEvents, really love them <3

    Im looking around on how to add validation so i can catch missing references and stuff.

    Do you think having a tree like (Jason's Storey) is a good way to go about it?
     
  34. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
    I'm not really sure what you're asking.

    If you're just wondering how you could display the results, a tree like that would probably be fine.
     
  35. ipotonic

    ipotonic

    Joined:
    Mar 9, 2019
    Posts:
    7
    So i can see and check if any UltEvent are broken. is there a simpler way?

    Also, how do i check if something inside UltEvent is broken? Like what makes it red?

    I found these, but i doubt they are correct.
    // if doesnt have a method name, has a target, but arguments 0, then red.
    // if target null and persistent calls 0 then red.
     
    Last edited: May 30, 2022
  36. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
    Try something like this:
    Code (CSharp):
    1.         public static bool HasError(UltEvent ultEvent)
    2.         {
    3.             var calls = ultEvent.PersistentCalls;
    4.             for (int i = 0; i < calls.Count; i++)
    5.             {
    6.                 var call = calls[i];
    7.                 var method = call.Method;
    8.                 if (method == null)
    9.                     return true;
    10.  
    11.                 if (!method.IsStatic && call.Target == null)
    12.                     return true;
    13.             }
    14.  
    15.             return false;
    16.         }
    You could also use method.GetParameters() and compare them to call.PersistentArguments if you want to go into full detail.
     
    ipotonic likes this.
  37. ipotonic

    ipotonic

    Joined:
    Mar 9, 2019
    Posts:
    7
    Hey so, i got validation working and its looking great. Now i want it to get one step further. To keep somewhere the references before losing them if it has an error, so i wanted to ask for any guidance.

    One way i wanted to try is this: upload_2022-6-12_23-32-3.png

    So for example im thinking, either Editor or PropertyDrawer that refreshes the a string value with the content of each ultevent index. And it keeps the references while editting.
    Now if something happens and the ultevent got error cause method name changed or whatever and it loses references, i have the string saved and i can see what it was doing with what values.

    Should i try and do it inside UltEventDrawer.?
     
  38. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
    It should be possible to still show the PersistentArguments of a call with a missing method, which would be done somewhere in PersistentCallDrawer. The part that displays the name of each argument would need to be modified since the names come from the method. I'm not sure what else would need attention.
     
  39. ipotonic

    ipotonic

    Joined:
    Mar 9, 2019
    Posts:
    7
    okay i'll try to do it there! thanks
     
  40. ipotonic

    ipotonic

    Joined:
    Mar 9, 2019
    Posts:
    7
    Alright! I just did this to display the argument info, its fine without param names. upload_2022-6-13_20-1-11.png

    The only issue is that i cant figure out how to fix the height issue ;p
    upload_2022-6-13_20-2-11.png
     
  41. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
    The height is calculated in the GetPropertyHeight method.
     
  42. Evallis

    Evallis

    Joined:
    Nov 6, 2012
    Posts:
    20
    Hello Kybernetik! I just have a quick question for you, how would I go about making the text box for string parameters 2 or 3 lines in height instead of 1? I have some events that need quite a bit of text and having more room to type would be awesome! Thanks!
     
  43. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
    1. Open PersistentArgumentDrawer.
    2. See how OnGUI is checking which PersistentArgumentType the argument is.
    3. Do something similar in GetPropertyHeight when the type is String.
    4. That might be all you need, but if the field doesn't adjust to fit the new height take a look at DoStringGUI as well.
     
  44. Evallis

    Evallis

    Joined:
    Nov 6, 2012
    Posts:
    20
    Alright I found where OnGUI is checking the type and I checked out DoStringGUI, but i'm a little confused how to edit GetPropertyHeight? I never really work with editor scripts so i'm not too sure how all the functions work together. Thanks!
     
  45. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
    The return value of GetPropertyHeight determines how many pixels high the property will be. So you just need to check if the given property is a string and if so, return
    3 * EditorGUIUtility.singleLineHeight
    to make it take up 3 lines instead of the usual 1.
     
  46. Evallis

    Evallis

    Joined:
    Nov 6, 2012
    Posts:
    20
    I see, so where do the properties use GetPropertyHeight to get their heights? I don't see any references to GetPropertyHeight in PersistentArgumentDrawer, or am I missing something?
     
  47. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
    It's called by Unity, which uses the returned value to set the height of the Rect which is then passed into OnGUI.
     
  48. Evallis

    Evallis

    Joined:
    Nov 6, 2012
    Posts:
    20
    Okay so would I use something like an override float to make my changes? I tried making this:
    Code (CSharp):
    1. public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    2.         {
    3.             var propertytype = property.FindPropertyRelative(Names.PersistentArgument.Type);
    4.             if ((PersistentArgumentType)propertytype.enumValueIndex == PersistentArgumentType.String)
    5.             {
    6.                 return 3 * EditorGUIUtility.singleLineHeight;
    7.             }
    8.             else
    9.             {
    10.                 return base.GetPropertyHeight(property, label);
    11.             }
    12.         }
    but it does not seem to change anything in the inspector, am I on the right track?
     
  49. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,400
    That method is correct, but it looks like it wasn't set up to even check what height each property is since they were all one line.

    Go to PersistentCallDrawer and find this line:
    var argumentProperty = DrawerState.Current.PersistentArgumentsProperty.GetArrayElementAtIndex(i);

    Add this after it:
    area.height = EditorGUI.GetPropertyHeight(argumentProperty, ArgumentLabel);

    Then go to the GetPropertyHeight method (still in PersistentCallDrawer) and replace its last line with:
    Code (CSharp):
    1.             var height = EditorGUIUtility.singleLineHeight;
    2.             for (int i = 0; i < callProperty.arraySize; i++)
    3.             {
    4.                 var argumentProperty = callProperty.GetArrayElementAtIndex(i);
    5.                 height += Padding + EditorGUI.GetPropertyHeight(argumentProperty, label);
    6.             }
    7.             return height;
    That would make the text field take up the right area, but the regular text field doesn't word wrap and pressing enter deselects the field instead of making a new line. So you also need to go to the DoStringGUI method in PersistentArgumentDrawer and replace the middle line:
    Code (CSharp):
    1.           // Replace this: var value = EditorGUI.TextField(area, label, s.stringValue);
    2.           area = EditorGUI.PrefixLabel(area, label);
    3.             var value = EditorGUI.TextArea(area, s.stringValue);
     
  50. Evallis

    Evallis

    Joined:
    Nov 6, 2012
    Posts:
    20
    That worked! Thank you so much you're the best! :D