Event System - Dispatcher

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.

1 Like

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?

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.

Awesome. Yeah, I wasn’t entirely sure what the Profiler was telling me. MD vs. SMess vs. Direct.

Thanks for the response. Purchased!

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. :slight_smile:

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!

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.

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.

1 Like

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?

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.

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!

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.

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.

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.

Awesome. Thanks for your help and for a great asset.

1 Like

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 :wink:

When you’re sending messages instantly, you can simply release the message after you call send. For example:

// Send a custom message to everyone
MyCustomMessage lMessage = MyCustomMessage.Allocate();
lMessage.Type = "CUSTOM";
lMessage.MaxHealth = 100;
lMessage.CurrentHealth = 50;
lMessage.Delay = 0f;
MessageDispatcher.SendMessage(lMessage);
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:

lMessage.Release();

That should make things consistent.

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.

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.

Ok gotcha. Thanks for the quick update!

1 Like