Search Unity

Event System - Dispatcher

Discussion in 'Assets and Asset Store' started by Tryz, Dec 6, 2015.

  1. Tryz

    Tryz

    Joined:
    Apr 22, 2013
    Posts:
    3,402
    This is the official (long over-due) thread for the Event System - Dispatcher.

    Event and message dispatching is a critical part of any game. Message dispatching ensures that game objects are able to communicate in a consistent, reliable, and efficient way. The Event System - Dispatcher (dispatcher for short) does exactly this.
     
    BackwoodsGaming likes this.
  2. Lavellotron

    Lavellotron

    Joined:
    Mar 25, 2016
    Posts:
    4
    Hi Tim,

    I'm considering buying Event System. Looks solid and great reviews! I have a question about performance. Is Event System performant enough to use for a situation where I want to dispatch a message once every few frames? For instance, spawn controllers that listen to a "heartbeat" and need to spawn every 10 frames or so?
     
  3. Tryz

    Tryz

    Joined:
    Apr 22, 2013
    Posts:
    3,402
    Yeah, that shouldn't be an issue.

    The real factor will be the computers you're running on. For me, the web demo shows 500 messages in 1.3 milliseconds. Since each frame typically takes 16.6 milliseconds (60 FPS), you should be fine.
     
  4. Lavellotron

    Lavellotron

    Joined:
    Mar 25, 2016
    Posts:
    4
    Awesome. Yeah, I wasn't entirely sure what the Profiler was telling me. MD vs. SMess vs. Direct.

    Thanks for the response. Purchased!
     
  5. Tryz

    Tryz

    Joined:
    Apr 22, 2013
    Posts:
    3,402
    Thanks.

    MD = Message Dispatcher (this asset)
    SMess = Unity's native SendMessage call
    Direct = Directly calling the function instead of using the message system.

    I probably should note that somewhere... haha. :)
     
  6. Lavellotron

    Lavellotron

    Joined:
    Mar 25, 2016
    Posts:
    4
    What would be the best way to listen/send messages between Components on the same GameObject? I'm building some reusable Components that work together when they share the same GameObject. For simplicity's sake, let's say I have a component that sends values from a Sine wave. Then you can choose to add one or more listening components that use the Sine data to either change the GameObject's color or move it etc., these being modular and separate classes.

    So you could have a Cube that has the Sine component attached, as well as the ColorChanger and the MoveMe component also attached listening to the Sine component. You could then have a Sphere elsewhere in the scene graph that has a similar setup but with differing color and position values.

    The Sine component would send a message VALUE_CHANGED, and the other components attached to the same GameObject would respond, but only to that instance of the Sine component and not others elsewhere in the scene.

    Make sense? Thanks in advance for your help!
     
  7. Tryz

    Tryz

    Joined:
    Apr 22, 2013
    Posts:
    3,402
    No difference.

    Page 15 of the User's Guide is an example of sending a message to yourself. It doesn't matter that the ColorChanger or MoveMe are different components on the same GO... they are still listening for the GO (based on the GO's name...so that needs to be unique).

    If you need something more granular, I'd just use the standard message filter. Since the filter is a string, you can use a unique GO value and some additional qualifier.

    However...
    If I follow your example, the receivers know there is only one sender and they are tightly connected to him. If that's right, you could also just use a delegate. So at Start() your ColorChanger and MoveMe would look for the SineWave on the gameObject property. If found, they would register with the SineWave's delegate. When SineWave calls its delegate only the ColorChanger or MoveMe on that GO is called. That's the cleaner approach.

    The MD becomes more helpful when the receivers don't know who the senders are or don't have access to them. In this case, they are actually tightly bound by the same "gameObject" property.
     
  8. Lavellotron

    Lavellotron

    Joined:
    Mar 25, 2016
    Posts:
    4
    Well put. Yes, in my example the objects are more tightly coupled by design. I'll also be using MD for more general and loosely coupled messages.
     
    Last edited: Mar 27, 2016
    Tryz likes this.
  9. Stevepunk

    Stevepunk

    Joined:
    Oct 20, 2013
    Posts:
    205
    I just got this asset on lv11 and still a Unity beginner (even after several years) so I'm not familiar with the built-in messaging system. What are the actual use cases for this? Do you have any demonstration videos?
     
  10. Tryz

    Tryz

    Joined:
    Apr 22, 2013
    Posts:
    3,402
    I don't have any videos up, but you probably saw the documentation here:
    http://www.ootii.com/Unity/Dispatcher/MDGuide.pdf

    It really is as simple as I show in the documentation examples.

    Since it's a messaging system, it's really up to you how you use it. Its a way of letting objects communicate even if they really don't know who they are communicating to. You could use it for combat or health changes, trap resolution, entity state notifications, etc.

    If you google for "message handling" or "message dispatching", you'll find lots more info on the subject.

    One comment I'd make is never force yourself to use something just because it's there. If your game has tightly coupled objects, you may not need message dispatching. However, if you find your objects have references to lots of other objects all over the place... message dispatching is a good design.
     
  11. JFR

    JFR

    Joined:
    Feb 21, 2014
    Posts:
    65
    I am currently building a custom message class and instead of using MyCustomMessage.cs, I created one that I named sbx_MouseEvent.cs, copy pasted the code and changed all references in the script to the new class. Everything works well, and by looking at the code I assume that a list of mouse event messages is created as new ones are instantiated, and it is up to me to properly use OnRelease() to properly dispose of each messages when I am done. This function, amongst other things, sets the IsSent and IsHandled flags to false, so in theory any other listener still waiting for the message after OnRelease has been called should NOT receive it. This part doesn't work, nor directly setting the IsHandled() flag in a listener to achieve the same effect. But in the documentation, you clearly state that it should be the case.

    What goes?

    Thanks for your help!
     
  12. Tryz

    Tryz

    Joined:
    Apr 22, 2013
    Posts:
    3,402
    Hey @JFR

    I think I'm following. If you're wanting to pool your custom messages (like I do with MyCustomMessage.cs), you will have to call Release() in order to release your instance back to the pool for re-use. In the case of MyCustomMessage, there's a custom pool for that message type.

    If you've got a lot of messages flying around, using a pool is way more efficient than creating a new instance each time.

    The UI.cs file shows an example of my message handler doing this... see OnCustomMessageReceived().

    Actually, it sets them to 'true'
    See Message.cs lines 145 and 166,
    See MyCustomMessage.cs lines 60 and 81

    I think the thing you missed is that your listener has to respect the "IsHandled" flag. In the documentation (page 12), I wrote: "This kind of “IsHandled” logic is for you to code as needed."

    What I mean is that all listeners will get the message (based on filters and such). You need logic in your listeners to watch the IsHandled flag and not process if it's set to true. This gives you the ability to ignore the IsHandled flag if you need. You have complete control.

    I hope that makes sense.
     
    Last edited: Aug 5, 2016
  13. JFR

    JFR

    Joined:
    Feb 21, 2014
    Posts:
    65
    Yeah, totally right about the flags, my bad.. Thanks for the quick reply and the email as well. Just to clarify: is one pool created for each each custom message I create (i.e. replacing MyCustomMessage.cs by MyClass.cs and modifying the MyCustomMessage script so that MyClass is used in all its methods instead) or is there a common pool that regroups all custom messages deriving from the message class?

    I like the system so far. Will leave positive review.
     
  14. Tryz

    Tryz

    Joined:
    Apr 22, 2013
    Posts:
    3,402
    Yes. That's the better approach.

    Technically, you could use a single base-class pool. However, when you call Allocate() you might not get the exact type back that you'd expect. So, it's better that each type have their own pool.
     
  15. JFR

    JFR

    Joined:
    Feb 21, 2014
    Posts:
    65
    Awesome. Thanks for your help and for a great asset.
     
    Tryz likes this.
  16. JFR

    JFR

    Joined:
    Feb 21, 2014
    Posts:
    65
    Actually, I do have another question. When working with multiple listeners, how do I know when to call the release() function? There should be some way to query if all listeners have processed the message? If I call it too early, it sets my event's variables and properties to 0, which can potentially lead to all kinds of problems for subsequent listeners (for that same event). And if I never call it at all, does this simply mean that the event's properties are never reinitialized? There should be an option to automatically release a message once all listeners have processed it? Am I making any sense? Or wouldn't we be better off creating a new object from a custom class and passing that on instead?

    Just a bit hesitant here as to the proper bullet proof way to properly release messages.

    Thanks ;)
     
  17. Tryz

    Tryz

    Joined:
    Apr 22, 2013
    Posts:
    3,402
    When you're sending messages instantly, you can simply release the message after you call send. For example:

    Code (CSharp):
    1. // Send a custom message to everyone
    2. MyCustomMessage lMessage = MyCustomMessage.Allocate();
    3. lMessage.Type = "CUSTOM";
    4. lMessage.MaxHealth = 100;
    5. lMessage.CurrentHealth = 50;
    6. lMessage.Delay = 0f;
    7. MessageDispatcher.SendMessage(lMessage);
    8. MyCustomMessage.Release(lMessage);
    For delayed messages, you could add a semaphore or counter. But, I think I can implement a more elegant way.

    I've added a "Release()" method to the IMessage interface. So, now after I send all delayed messages, I'll check the "IsHandled" flag. If set, I'll call the Release() method of your message. In there, you can clean up and release as needed.

    In the case where you allocate an instant message (like above), you'll still need to release it. However, you can replace line #8 with:
    Code (CSharp):
    1. lMessage.Release();
    That should make things consistent.
     
    Last edited: Aug 8, 2016
  18. JFR

    JFR

    Joined:
    Feb 21, 2014
    Posts:
    65
    Great idea about adding it to the interface, this way I can just know delayed messages will be automatically reinitialized based on what I put it my Release() method.

    But I'm not quite sure about calling Release() during the SendMessage(), since during the release all variables would be reset to set to (as Clear() is called as part of the Release() function). So wouldn't all variables contained in the custom message class be set to 0 before any listener would get a chance to get the real value?

    Thanks for your answers.

    Edit: ok nevermind about the first part, just did your example and the sequencing works, although I'm guessing in some edge cases there will still be listeners waiting to process the message. I'll let you know if that happens, not quite sure how multiple listeners are handled in the stack and in terms of order of execution.
     
    Last edited: Aug 9, 2016
  19. Tryz

    Tryz

    Joined:
    Apr 22, 2013
    Posts:
    3,402
    It would be fine. For this case, we're really only talking about messages with a delay...

    When the delay expires the message is sent to all listeners. After all listeners have processed, I look to see if the "IsHandled" flag is true. If so, then I'll call Release() on the message.

    So, the listeners will all have a chance to respond before I clear any values.
     
  20. JFR

    JFR

    Joined:
    Feb 21, 2014
    Posts:
    65
    Ok gotcha. Thanks for the quick update!
     
    Tryz likes this.
  21. Vanocore

    Vanocore

    Joined:
    Jul 15, 2013
    Posts:
    4
    Hi there, i just build your example scene with WebGl build target and it doesn't work for "TARGET" and "FILTER" messages.

    So there is some problem with:
    Spheres Blue
    Cubes Blue
    Lead Sphere Red
    Lead Cube Red

    I see "Nope, no listener for .." in console. In other cases everything is ok
    Any solution with that?
     
  22. Tryz

    Tryz

    Joined:
    Apr 22, 2013
    Posts:
    3,402
    Unfortunately, it's a Unity issue with packages, tags, and builds. This isn't really an issue in your projects because you'll add tags.

    Solution:
    1. Open the demo scene and select a cube

    2. Press "Add Tag..." in the inspector, and add the following tags:
    Sphere
    Cube
    Cylinder

    3. Set the tag on the appropriate objects.

    Then you can build and it will work:
    http://www.ootii.com/Unity/Dispatcher/ED_Demo_2/index.html

    Issue:
    If you open the scene and click on a cube, you'll see the inspector's Tag drop-down shows: Tag = Cube.

    If you open the Tag drop-down and select "Add Tag...", you'll see no tags actually exist.

    When running in the editor, Unity seems to find the "Cube" tag just fine. However, when running in an actual build the empty list is an issue.

    I actually have the list filled in my project, but it doesn't export to the package (at least with Unity 5.4).
     
    Vanocore likes this.
  23. MatthewHarmon

    MatthewHarmon

    Joined:
    Mar 5, 2015
    Posts:
    24
    Hey Tim:

    Love your messaging implementation. Quick question - what was the rationale to spin in a coroutine in MessageDispatcherStub.Start(). Do you need the frame count increment to be precisely done at EndOfFrame? i.e. incrementing the frame count in Update was not good enough?

    And if I am only using "immediate" messages, I assume I can remove the coroutine?
     
  24. Tryz

    Tryz

    Joined:
    Apr 22, 2013
    Posts:
    3,402
    Thanks :)

    Right. Since there's no guarantee when the MD's Update() function will be called compared to SendMessage()... I'm using the counter to ensure we really are on the next frame.

    The only other thing to watch out for are listeners that you add or remove with a delay.

    For example, when removing a listener I typically delay and remove it at the end of the Update(). This way if someone removes a listener in response to the message, I don't modify the list of listeners while looping through the list.

    If you use AddListenter and RemoveListener with the "Immediate" parameter set to "true" as well as only immediate messages... you should be fine to remove the co-routine.
     
  25. MatthewHarmon

    MatthewHarmon

    Joined:
    Mar 5, 2015
    Posts:
    24
    Cool, thanks! And thanks for the quick replay. BTW: I'd vote for adding this signature to SendMessage. I'd expect it's a pretty common use.

    ///<summary>
    /// Create and send a message object
    ///</summary>
    ///<param name="rType">Type of message to send</param>
    ///<param name="rData">Data to send</param>
    public static void SendMessage(string rType, object rData)
    {
    // Create the message
    Message lMessage = Message.Allocate();
    lMessage.Sender = null;
    lMessage.Recipient = "";
    lMessage.Type = rType;
    lMessage.Data = rData;
    lMessage.Delay = EnumMessageDelay.IMMEDIATE;

    // Send it or store it
    SendMessage(lMessage);

    // Free up the message since we created it
    lMessage.Release();
    }
     
  26. Tryz

    Tryz

    Joined:
    Apr 22, 2013
    Posts:
    3,402
    Done. I've added this:

    Code (CSharp):
    1. public static void SendMessageData(string rType, object rData, float rDelay = 0f)
    I have to change the name or it collides with other versions. This will go out next update. :)
     
    Last edited: Aug 13, 2016
  27. Vanocore

    Vanocore

    Joined:
    Jul 15, 2013
    Posts:
    4
    It is now clear. Thanks for the help Tryz
     
    Tryz likes this.
  28. Fatbars

    Fatbars

    Joined:
    Jun 14, 2016
    Posts:
    3
    Hi
    My setup is a UNET situation. After the game start, I have problems getting the clients to load and sync with the server. (eg. when the server change scene, the client does not update).
    Will I be able to use Dispatcher to get the clients to change scene when activated in the server side?

    thx
     
  29. Tryz

    Tryz

    Joined:
    Apr 22, 2013
    Posts:
    3,402
    The Dispatcher wasn't built to do client server communication.

    I haven't done anything with UNET or client-server. So, I doubt that kind of communication would work.
     
  30. LostPanda

    LostPanda

    Joined:
    Apr 5, 2013
    Posts:
    173
    @Tryz how to send msg to across scene?
     
  31. Tryz

    Tryz

    Joined:
    Apr 22, 2013
    Posts:
    3,402
    The dispatcher, senders, and listeners are all scene objects. Based on how Unity works, when a new scene is loaded the old scene objects are destroyed.

    The dispatcher uses Unity's DontDestoryOnLoad function. So, it won't be destroyed when a new scene loads... but your senders and listeners will. You might be able to send messages across scenes if all your listeners are flagged with DontDestroyOnLoad as well, but that seems like a bad design.

    This asset really wasn't built to send messages across different scenes. Unfortunately, it's not a feature I support or promote.
     
  32. LostPanda

    LostPanda

    Joined:
    Apr 5, 2013
    Posts:
    173
    thanks!
     
    Tryz likes this.
  33. arvzg

    arvzg

    Joined:
    Jun 28, 2009
    Posts:
    619
    Hi @Tryz ,

    I have a couple of questions

    1. Playing around with this asset now, I'm wondering - is there a way to get a list of things that are listening to a given message type?
    2. I've read through the documentation and this forum, but I'm still not completely understanding what the IsHandled flag is for. In my simple test of Message sending and listening it seems everything is working fine even without setting this flag. Can you explain again why we need to set this flag?
     
    Last edited: Dec 28, 2016
  34. Tryz

    Tryz

    Joined:
    Apr 22, 2013
    Posts:
    3,402
    I don't expose anything publicly, but you can add code to look at MessageDispatcher's mMessageHandler property. This is a dictionary that holds the message types and handlers.

    It's actually there for your use. Since multiple handlers could process the same message, it gives you a way to flag that the message has been processed. In this case, follow-on handlers don't need to process it. Of course this is all up to you.

    If you don't need it for anything, I'd just have your handler set the value to 'true'.

    When messages are delayed, I do use the flag to know that I can dispose of the message itself.
     
  35. arvzg

    arvzg

    Joined:
    Jun 28, 2009
    Posts:
    619
    Ahh right, I get it now. Thanks @Tryz
     
    Tryz likes this.
  36. MichaelNielsenDev

    MichaelNielsenDev

    Joined:
    Apr 17, 2014
    Posts:
    10
    Hey @Tryz, thanks for making this. Loving it so far.

    Quick question: If I want to reference the sender of a SendMessage, how would I do that? I tried casting rMessage.Sender as a GameObject so I can reference it later, but I keep getting invalid cast exceptions. What should I do?
     
  37. Tryz

    Tryz

    Joined:
    Apr 22, 2013
    Posts:
    3,402
    Awesome. :)

    The Sender is just an "object". So you should be able to do something like this:

    GameObject lTheSender = rMessage.Sender as GameObject;

    I don't always set the Sender. It just depends if I have enough information to do it. So, depending on the SendMessage() function you use, I may not know the sender.

    The other thing you can do is pause your app in the debugger and look at the Sender property to make sure it's actually set.
     
  38. MichaelNielsenDev

    MichaelNielsenDev

    Joined:
    Apr 17, 2014
    Posts:
    10
    Thanks for the quick reply!

    That seemed to fix the casting exception, but now ITheSender is returning null when I try to reference it or access the name of the GameObject.

    For my own game project, it's important that an npc be aware of the identity of the sender of an event, so that's why I'm trying to access who the sender is.
     
  39. Tryz

    Tryz

    Joined:
    Apr 22, 2013
    Posts:
    3,402
    There are several versions of the SendMessage() function. Some get passed the Sender, but some don't. So, it depends on which version you're using.

    In the end, you have to tell the function who the sender is. Otherwise, the Message Dispatcher doesn't know.

    If you need to, you can allocate your own message, set the Sender yourself, and then send the message.
     
  40. MichaelNielsenDev

    MichaelNielsenDev

    Joined:
    Apr 17, 2014
    Posts:
    10
    Right, I understand that there are different SendMessage() functions. I am using one of the ones that includes the sender.

    I used the debugger and it seems that the problem is that the Sender reference being used is actually the component script in which the SendMessage() is called, not the GameObject attached to that script.

    I solved my issue by casting the Sender as a Monobehaviour and then grabbing a reference to its GameObject.

    Thanks!
     
    Tryz likes this.
  41. ZJP

    ZJP

    Joined:
    Jan 22, 2010
    Posts:
    2,649
    Hi,

    Although I believe you about performance, I am not comfortable with strings. Is this possible to do something like this ?:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using com.ootii.Messages;
    5.  
    6. public class Sample : MonoBehaviour
    7. {
    8.    void Start()
    9.    {
    10.        int Msg = 10;
    11.        Vector4 Value = new Vector4( 1.0f, 2.0f, 3.0f, 4.0f);
    12.        MessageDispatcher.AddListener(Msg, OnStarted, true);
    13.        MessageDispatcher.SendMessage(this, Msg, Value, 0);
    14.    }
    15.  
    16.    void OnStarted(IMessage rMessage)
    17.    {
    18.    }
    19. }
    20.  
    21.  
    I bought the tool months ago, but I have not yet had time to use it.. Until now... :p
     
    Last edited: Mar 10, 2017
  42. Tryz

    Tryz

    Joined:
    Apr 22, 2013
    Posts:
    3,402
    Not really. That's because the listeners are still identified by a key (which is a string).

    You would have to change the way I store the listeners and have them identified by an int in order to get rid of the string based keys.
     
  43. ZJP

    ZJP

    Joined:
    Jan 22, 2010
    Posts:
    2,649
    So, it's possible IF i modify the source. I hope this will not be too complicated. :D
     
  44. Tryz

    Tryz

    Joined:
    Apr 22, 2013
    Posts:
    3,402
    Oh sure.

    I'm using the string as a key to the dictionaries. So, if you modify the dictionaries where I store the listeners and then add a function or two for SendMessage(), you can.

    I don't think it would be too hard. I just can't modify for existing users.
     
  45. ZJP

    ZJP

    Joined:
    Jan 22, 2010
    Posts:
    2,649
    Of course. I will 'make' a sort of 'personnal version' for my main project.
    Thanks. :cool:
     
    Tryz likes this.
  46. AbyssWanderer

    AbyssWanderer

    Joined:
    Mar 15, 2016
    Posts:
    77
    Hi all.

    I've purchased Event System Dispatcher long ago and finally got my programmer using it (he was busy with other things before)

    We have two questions:
    1) Why is this asset more perfomant then Unity's event system?
    I totally believe you and other reviews (which is why I purchased it), but curiosity forces us to ask this questions =)

    2) We have almost clean project, so it is not difficult for us to make an improvement yet.
    Will we really benefit from what ZJP mentioned:
    .. and what Tryz answered:
    We are going to send quite many messages every other frame.

    Thanks in advance.
     
  47. Tryz

    Tryz

    Joined:
    Apr 22, 2013
    Posts:
    3,402
    Hey @AbyssWanderer :)

    Unfortunately, I don't have a good answer for this because I'm not sure what Unity is doing it under the hood. I'm using a couple of nested dictionaries to hold the listeners and that has worked out well.

    You can certainly change the code to use ints instead of strings. Int look ups will be faster, but we're talking about fractions of milliseconds.

    For example here's a couple of tests...

    With 500 samples and 50 look-ups (in one frame):
    Int keys = 0.175 ms
    String keys = 0.184 ms
    4.8% improvement with ints

    With 2,000 samples and 200 look-ups (in one frame):
    Int keys = took 0.379 ms
    String keys took 0.446 ms
    15.0% improvement with ints

    Each project is different, but I felt that for the majority of users strings would be more usable. I can totally understand if that's not the case for your project. In that case, you would want to re-factor the code.

    I hope that helps.
     
    Last edited: Apr 21, 2017
    hopeful likes this.
  48. AbyssWanderer

    AbyssWanderer

    Joined:
    Mar 15, 2016
    Posts:
    77
    Thank you for clarifying all this.
    Probably fractions of miliseconds increase are not worth bothering, even for our amount of events per frame. If FPS is low, you usually need to fix it with few major things, rarely you sctracth micro- and nano-seconds all over your project.
     
    Tryz likes this.
  49. ben-rasooli

    ben-rasooli

    Joined:
    May 1, 2014
    Posts:
    40
    I'm stucked. Here's the situation:
    - load scene2
    - MessageDispatcher.AddListener ("EventName", handler);
    Everything works fine so far
    - load scene1
    - load scene2
    - MessageDispatcher.AddListener ("EventName", handler);
    When an event received, this error shows up:
    `MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it.`

    I fixed the same issue on a different object by doing this:
    Code (CSharp):
    1. void OnDisable ()
    2. {
    3.     MessageDispatcher.RemoveListener("EventName", handler);
    4. }
    But the problem is that I can't do the same on this object. It needs to keep listening even after it's disabled. Because it's enabled by the event.

    I think when I add a listener for the second time without removing it first, MessageDispatcher register a new listener instead of overriding the existing one. And when there is an event, the dead listener gets called and cause the issue.

    Any idea?
     
  50. Tryz

    Tryz

    Joined:
    Apr 22, 2013
    Posts:
    3,402
    Damn. Looks like my response was deleted during the update or hack too. :(

    You have to remove the listener when the object is destroyed or the scene shuts down. Otherwise, I don't know that the GameObject is no longer valid. To do this, you can use:
    MessageDispatcher.RemoveListener() - as you show
    MessageDispatcher.ClearListeners()

    The last one will remove them all.

    You also can't just replace the listener. That's because when the new scene is loaded the GameObjects represent new instances and a comparison won't match. So, you want to remove the old one and add the new one.

    I just confirmed that disabled GameObjects (that are not destroyed) do get messages. So, you can enable it off of a message.

    I also cache the removal of listeners. So, you can remove a listener in response to a message too.

    My only other option would be to test the GameObject before every send for every message for all users. The performance impact of that doesn't seem worth it on the rare occasion that the listener isn't removed gracefully.

    I hope that helps.
     
    ben-rasooli likes this.