MessageRouter

Author: Fernando Zapata (fernando@cpudreams.com)

Description
This subscription based messaging/event system provides an efficient method for broadcasting game events to interested parties while maintaining loose coupling. It has several features which may make it a good fit for your game:


 * Delayed Notification: messages can arrive immediately or delayed by n seconds or n frames
 * Delivery Stages: messages can be sent immediately or delayed until the next Update, FixedUpdate, LateUpdate, or EndOfFrame
 * Message Filtering: subscriptions can be as broad or as narrow as you need them to be, you can subscribe to all messages sent to you, to anyone, sent from someone, sent from anyone, of a particular type/class, of any type/class, with any tags, or with particular tags
 * Tags: messages can have zero or more tags that categorize it and can be filtered by those tags
 * Receiver Assertions: messages can be marked as requiring a receiver and subscribers can be marked as hidden receivers the message router will generate an error if a message requiring a receiver is not received by at least one non-hidden receiver

Usage
The MessageRouter is flexible and can handle many different scenarios. As an example, imagine an air combat game in which pilots communicate through our MessageRouter. A pilot might broadcast (shout) an "Under attack!" message if they feel threatened.

First you will need to add the MessageRouter.js component to at least one GameObject. The MessageRouter is usually a singleton but you are free to have instances specific to particular sub-systems.

Next lets create a "Under attack!" message to send from pilots under attack.

To create a new message type create a sub-class of the MessageRouter's Message class. A message can contain any additional data that is specific to this new message type. It can also contain any additional convenience constructors and methods that you deem necessary. In this example I've kept things simple and have only added a single attribute with the call-sign of the pilot that has sent this distress message.

After creating the message type we need to broadcast it to any interested parties. Hopefully there is a friendly nearby that is willing and able to react.

To send a message create an instance of the previously defined UnderAttackMessage and assign it a re-usable message header. A single message header can be shared by multiple message instances and types. A message header is like the front of an envelope and is used to route the message to subscribers. By convention I've exposed the message header as an attribute of this script so I can configure it using the Unity Inspector. There I can specify that a receiver is not required (header.requireReceiver = false) and that the message should be delivered 0.2 seconds after it was sent (header.deliveryTime = 0.2). The message is delayed as a way to simulate the pilots reaction time.

Once a message has been created and initialized sending it is easy. Just call SendMessage on the message router with the newly created message. After a message has been sent it will be discarded if there are no interested subscribers. In order to receive messages you must create a subscription.

To subscribe to a message you need a subscription object. By convention I've exposed the subscription as an attribute of this script so I can configure it using the Unity Inspector. I also set the subscription's handler to a method that takes an UnderAttackMessage as its only parameter. I set the subscription type to UnderAttackMessage so that the handler is only called for messages of this type.

Once a subscription has been created and initialized subscribing is easy. Just call Subscribe on the message router with the newly created subscription (use Unsubscribe to remove a subscription at any time). After a subscription has been made any messages matching the subscription will be sent to the OnUnderAttackMessage method. In this case, the "Under Attack!" message is heard by an opponent of the Red Baron and unless a friendly subscriber also received this message the Red Baron may have finally met his match.

This example shows an idiomatic way of using the MessageRouter. By exposing the message header and subscription objects as attributes you can easily configure the message routing in many different ways. For example, you could add an AI tag to "Under Attack!" message and similar messages and have a subscriber subscribe to all AI tagged messages to produce an AI log for debugging. For details on features not used in this particular example please read the doc comments of the relevant class (ie. MessageHeader, Message, MessageSubscription, and MessageRouter). If you have any questions feel free to contact me directly at fernando@cpudreams.com.

Code
There are two files MessageRouter.js and CompactList.js. MessageRouter is the only MonoBeaviour component and is implemented at the end of MessageRouter.js. MessageHeader, Message, and MessageSubscription are plain helper classes and are also implemented in MessageRouter.js. The enum MessageTag is also included in MessageRouter.js and should be modified to add additional user defined tags (you may prefer to keep the enum in your own separate Javascript file). CompactList.js implements a custom data structure that is well suited for storing delayed messages and is used internally by MessageRouter. MessageRouter.js also includes DelayedMessageList which likewise is for internal use by MessageRouter.