Search Unity

Problems with UnityEventTools.AddPersistentListener: ArgumentException is thrown

Discussion in 'Scripting' started by QFSW, Aug 19, 2017.

  1. QFSW

    QFSW

    Joined:
    Mar 24, 2015
    Posts:
    2,906
    Hi all,

    I'm trying to use the UnityEvent system so that my EditorScript can serialise Actions and save them to the object for Event binding, but I'm having trouble with the system. I have this code:
    Code (CSharp):
    1. public BindedEvent CreateAndBindEvent(EventTriggerType DesiredTriggerType, OverloadEventBinder DesiredOverload, object[] MethodParamaterValues)
    2.             {
    3.                 //Creates invokable action and name
    4.                 UnityAction<object> Callback = DesiredOverload.CreateInvokableAction((object[])MethodParamaterValues.Clone());
    5.                 string EventName = DesiredOverload.CreateEventName(MethodParamaterValues);
    6.                 UnityObjectEvent CallbackEvent = new UnityObjectEvent();
    7.                 UnityEventTools.AddPersistentListener(CallbackEvent, Callback);
    8.  
    9.                 //Binds the event
    10.                 return new BindedEvent(EventName, DesiredTriggerType, CallbackEvent, ClassType);
    11.             }
    However when I run it, I get a rather unhelpful exception thrown
    Code (CSharp):
    1. ArgumentException: Could not register callback <>m__0 on . The class null does not derive from UnityEngine.Object
    2. UnityEngine.Events.UnityEventBase.ValidateRegistration (System.Reflection.MethodInfo method, System.Object targetObj, PersistentListenerMode mode, System.Type argumentType) (at /Users/builduser/buildslave/unity/build/Runtime/Export/UnityEvent.cs:841)
    3. UnityEngine.Events.UnityEventBase.ValidateRegistration (System.Reflection.MethodInfo method, System.Object targetObj, PersistentListenerMode mode) (at /Users/builduser/buildslave/unity/build/Runtime/Export/UnityEvent.cs:830)
    4. UnityEngine.Events.UnityEventBase.RegisterPersistentListener (Int32 index, System.Object targetObj, System.Reflection.MethodInfo method) (at /Users/builduser/buildslave/unity/build/Runtime/Export/UnityEvent.cs:866)
    5. UnityEngine.Events.UnityEvent`1[T0].RegisterPersistentListener (Int32 index, UnityEngine.Events.UnityAction`1 call) (at /Users/builduser/buildslave/unity/build/Runtime/Export/UnityEvent_1.cs:79)
    6. UnityEngine.Events.UnityEvent`1[T0].AddPersistentListener (UnityEngine.Events.UnityAction`1 call, UnityEventCallState callState) (at /Users/builduser/buildslave/unity/build/Runtime/Export/UnityEvent_1.cs:67)
    7. UnityEngine.Events.UnityEvent`1[T0].AddPersistentListener (UnityEngine.Events.UnityAction`1 call) (at /Users/builduser/buildslave/unity/build/Runtime/Export/UnityEvent_1.cs:60)
    8. UnityEditor.Events.UnityEventTools.AddPersistentListener[Object] (UnityEngine.Events.UnityEvent`1 unityEvent, UnityEngine.Events.UnityAction`1 call) (at /Users/builduser/buildslave/unity/build/Editor/Mono/Utils/UnityEventTools.cs:26)
    9. AMPInternal.EventBinding.ClassEventBinder.CreateAndBindEvent (EventTriggerType DesiredTriggerType, AMPInternal.EventBinding.OverloadEventBinder DesiredOverload, System.Object[] MethodParamaterValues) (at Assets/AudioManagerPro/Editor/AMPEventBinderInspector.cs:71)
    10. AMPInternal.Editor.AMPEventBinderInspector.OnInspectorGUI () (at Assets/AudioManagerPro/Editor/AMPEventBinderInspector.cs:258)
    11. UnityEditor.InspectorWindow.DrawEditor (UnityEditor.Editor editor, Int32 editorIndex, Boolean rebuildOptimizedGUIBlock, System.Boolean& showImportedObjectBarNext, UnityEngine.Rect& importedObjectBarRect) (at /Users/builduser/buildslave/unity/build/Editor/Mono/Inspector/InspectorWindow.cs:1229)
    12. UnityEditor.DockArea:OnGUI()
    13.  
    I'd really appreciate it if anyone here could help, I've posted the rest of the code that I feel is relevant but please ask for more if you need to.

    Code (CSharp):
    1. public UnityAction<object> CreateInvokableAction(params object[] MethodParamaterValues)
    2.             {
    3.                 if (MethodParamaterValues.Length != MethodParamaters.Length) { throw new ArgumentException("Incorrect paramater count passed.", "MethodParamaterValues"); }
    4.                 return (object TargetObject) => MethodOverload.Invoke(TargetObject, MethodParamaterValues);
    5.             }
    Code (CSharp):
    1. [Serializable]
    2.     public class UnityObjectEvent : UnityEvent<object> { }
     
    adrian-miasik likes this.
  2. QFSW

    QFSW

    Joined:
    Mar 24, 2015
    Posts:
    2,906
    Also, forgot to mention. This code is running inside a custom class as opposed to the scriptable object etc
     
  3. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    You're trying to serialize a Lambada to the persistent listeners which I'm fairly certain will never work. A Persistent Listener needs:
    • a UnityEngine.Object target its calling this from. (which GameObject, ScriptableObject, or Component class has this function/property, Lambadas don't belong to a class so the target is null)
    • Target's AssemblyQualifiedName (grabbed automatically when the target is set in the inspector, needed for polymorphism, since target is null this is also invalid)
    • a function name (which Lambada's don't have, you have to reference an actual method/property that the target has)
    • Trigger type (None, Editor And Runtime, Runtime only)
    • Mode (calculated and used internally based on which method you binded)
    UnityEventTools.AddPersistentListener appears to set the mode to dynamic since it supports more than one parameters and theres no fields where you can passing static fields, and because it also expects you to pass in a UnityEvent with the same parameter types.

    Thus to use AddPersistentListener properly you'll need a reference to the class and method group you wish to bind, and a reference to a UnityEvent of the same type. The UnityEvent created in-line means that it can only have one listener per event. Plus I'm not sure where you're passing that callback to but if its not referenced by any object in the scene or asset its going to get deleted before you have the chance to invoke it.
     
    adrian-miasik, QFSW and Kiwasi like this.
  4. QFSW

    QFSW

    Joined:
    Mar 24, 2015
    Posts:
    2,906
    Hmm, that sucks, thanks for the information.
    What I'm trying to do, to make this more informative.
    I have an editor script which generates a bunch of Actions. Each Action was bundled into a serializable class called BindedEvent; finally, this instance would be added to a list on a monobehaviour. So the end result was a unity Object that had a list of non Unity objects, each with their own Action. This worked perfectly other than the fact that the Actions couldn't serialize, so after searching around it appeared Unity events could be the solution, but evidently from what you've told me this is not the case? Or is storing the lambdas even possible in anyway at all?
     
  5. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    Just make an abstract Command class that derives from ScriptableObject and which has a public abstract void Execute(CommandData data).

    Code (CSharp):
    1. public abstract class Command: ScriptableObject
    2. {
    3.     public abstract void Execute(CommandData data);
    4. }
    Each of your special lambada actions (specifically MethodOverload) will instead be written as a new Command class overriding the Execute and the derived class will have a CreateAssetMenu attribute. right click and create the asset and then drag'n'drop into your monobehaviour's list. you don't even need to use UniterEventTools. you can even make a UnityEvent<CommandData> variable and then set up all command listeners via the insepctor, then when you invoke it'll run all the commands in the order you've serialized them. CommandData will simply be an abstract container which fields such as targetObject and methodParameters.
     
  6. QFSW

    QFSW

    Joined:
    Mar 24, 2015
    Posts:
    2,906
    Hmm, I may have completely misunderstood what you've meant, in which case I'm sorry, but doesn't this mean I'd need to create it in the editor manually rather than having the code generate them? Unfortunately I need to be able to generate it all in c# with just the method info and the parameters it will take
     
  7. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    exactly what are you trying to serialize? from the looks of it the code geneation itself can be moved to runtime.

    it just appears that the methodParameters are the only things that need to be serialized. system.Object is not serializable by unity. they can only be serializable if they are made up of primatives, derive UnityEngine.Object, or in the case of custom serializable classes have a UnityEngine.Object instance as their serialization root (for example the monobehavior with the list of serialized BindedEvent, the monobehaviour is the root/starting point from where the BindEvent's serialized data will be stored relative to).

    If you put those constraints on the MethodParameterValues then you can serialize that via unity. otherwise if it must be object[], you're gonna have to resort to serializing them manually to file/server.
     
  8. QFSW

    QFSW

    Joined:
    Mar 24, 2015
    Posts:
    2,906
    Hmm okay, if I were to take your suggested approach of binding it at runtime. I would need to serialize the following

    MethodParamaterValues:
    This is a system.object[], however it will only ever contain primitives, strings, unity objects (scriptable objects and mono behaviors) and built in serializable structs like vectors, so all serializable. Would this mean it's good to go or do I have to cast it to something else?

    Additionally, I would also need a system.type and a method info object; afaik these are not serializable?

    Thanks for taking the time to help with an alternative suggestion