Search Unity

Prevent resubscribing to an event

Discussion in 'Scripting' started by ThySpektre, Oct 21, 2019.

  1. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    Hello,

    Various scenes in my game subscribe to events conditionally. A problem occurs when I enter the scene again and it subscribes to the event again. (I had thought when the Object containing the code component was destroyed, so would have the subscription).

    It's not always easy to determine at the end of the scene which events have been subscribed to. I could follow around each subscription with a boolean and at the end of the scene unsubscribe based on these, but is there a way to see what you are already subscribed to upon entering a scene so as not to subscribe again?
     
  2. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,775
    You probably want to have the function that subscribes to the event also unsubscribe in OnDisable. Better to unsubscribe then immediately resubscribe, than to risk subscribing twice.

    Other option is to just set a "didSubscribe" flag to ensure that an event only gets subscribed to once in the life of that object.
     
  3. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    I'm having a hard time imagining a scenario where "just don't subscribe again" is the proper solution.

    If an object subscribes to an event and then gets destroyed without unsubscribing, you're going to have problems whether you make a new subscription or not, because when that event fires it's going to try to call a function on an object that doesn't exist anymore, which will throw an exception.

    You'd have to have some sort of situation where the original listener object still exists but somehow thinks that it's being newly created again..?


    AFAIK there is no way in C# for a listener to query an event and figure out whether it's subscribed, so if you only sometimes subscribe, I believe you'll have to track that yourself.


    Unsubscribing in OnDisable or OnDestroy might seem like a good idea, but if the thing you are subscribing to is also a MonoBehaviour, you run into problems when quitting your game that the thing you are subscribed to might get destroyed before you are, and then when you try to unsubscribe you'll get an exception for accessing a destroyed object.

    So to be safe, you also need an OnApplicationQuit event that sets a flag somewhere that you can check to see whether the application is in process of quitting when OnDisable/OnDestroy get called, so that you can skip the unsubscribe step in that case.

    (This seems like a poor design choice on Unity's part, but I don't know of a better workaround.)
     
  4. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    Sounds reasonable, combined with a bunch of "isSubscribed" booleans and Antistone suggested.

    One issue is the GameManager doing these subscribing is a singleton. Just adding an OnDisable event is throwing errors for it not being an instance of a class. Any ideas about how to properly call OnDisale on a singleton?
     
  5. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    Strangely, it wasn't.

    Understood, I've run into a few poor design choices on Unity's part. Does OnApplicationQuit fire before OnDisable when an application starts to quit?
     
  6. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    What is strange is this subscription survives the death and recreation of the object. Any such flag would not.
     
  7. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    I'm not clear what you mean to imply by "singleton". Lots of Unity programmers use "singletons" that are, in fact, MonoBehaviours that get instantiated and attached to a GameObject (but only once).

    I'm also not clear what problems you're having with OnDisable. OnDisable is a magic function that, if it's defined on a MonoBehaviour, will get automatically called at the correct time. But if you defined it in some class that was not a MonoBehaviour, that should still be legal; it won't have the magic functionality you want, but it would still be a valid function, so I'm not sure where the error is coming from. Is this a static class?

    If it's not a MonoBehaviour, then it's probably not being destroyed when you change scenes; and if it's a static class, then it by definition can't be destroyed at all! So it's not clear why it would even be recreated/reinitialized when you change scenes (but that would explain why your subscription from the previous scene isn't generating errors!)

    I think you need to go into more detail about how you've structured the code surrounding these events and subscriptions.

    Yes. (Or at least, it seems to in my tests.)
     
  8. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    "Singleton" as in the singleton pattern, as you noted.

    The problem with OnDisable() was a strange one that disappeared after an editor restart, and your combination of OnApplicationQuit and OnDisable seems to be working well so far. Thanks.
     
  9. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,330
    It is also possible to unsubscribe from an event right before subscribing to it like this:

    Code (CSharp):
    1. target.myEvent -= OnEvent;
    2. target.myEvent += OnEvent;
    When this is done, if you were subscribed to the event before, the subtraction assignment operator removes the prior subscription, and if you were not subscribed to the event before, then nothing happens.

    It is still most likely a good idea in your situation to keep track of your subscriptions with flags manually, and unsubscribe from the event during OnDisable - just letting you know that this is possible too, and might come in handy in some situations.
     
  10. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,980
    Im struggling to see what the issue is?

    Just keep track of if you have subscribed to something, and what you have subscribed to. Then use that info to unsubscribe when needed.

    Is that not what you are already doing? If not ,thats the general way to manage events and the only time you wouldnt keep track of that kind of stuff is if there is only a single event in the whole game and it only gets subbed once. In which case, why is it an event in the first place ;)
     
  11. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    Yes, that's clear.
     
  12. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    You don't need OnApplicationQuit. Usually checking if the publisher is not null before unsubscribing is enough. if the reference is already null then there is nothing the listener can do to clean itself from the publisher, it just has to assume that the publisher was already destroyed (which means it can't even invoke said event anymore) and GC'd automatically. The rouge binding is no longer the class's problem to clean up.

    So a null check is more general safeguard and will work even if the application is not quitting.