BooMessenger

From Unify Community Wiki
Revision as of 15:58, 16 February 2010 by Tortoise (Talk | contribs)

Jump to: navigation, search

Author: Ofer Reichman (tortoise)

Contents

Description

BooMessenger is an efficient and simple to use messaging platform. It facilitates lightweight dispatching of messages (events) between script components without the need of coupling. It is written in Boo and can be used by any scripting language. Messages are properly defined so that errors are caught during compilation. Messages can also be grouped hierarchially using inheritance for easy subscription.

Usage

Creating and using the messenger

Creating the messenger is easy. Simply instantiate it:

<boo> messenger = BooMessenger()</boo>

You might want to store it somewhere persistent. One way would be to attach it to a game object. However, in most cases it is better (or at least simpler) to use the singleton pattern if you only need one messenger. The singleton and messaging mechanics were decoupled: a separate God class was bundled for your convenience. It allows easy access to the messenger from anywhere using:

<boo> God.Inst.Hermes</boo>

Feel free to develop however you like. If you don't use the God class, however, mind that you change the appropriate line in the Message class.

Dispatching a message

To create and send a message simply instantiate a message class, provided with parameters, and voila!

<boo> MessageText("Hello!")</boo>

The message will be sent right away.

Listening to messages

You can subscribe to receive a message or a group of messages using the Listen function. It receives the message type and the listener, which is the component calling (self):

<boo> def OnEnable():

       God.Inst.Hermes.Listen(MessageText, self)</boo>

Note: The listening script must be attached to a game object.

You can unsubscribe using the StopListening function. It receives the same parameters:

<boo> def OnDisable():

       God.Inst.Hermes.StopListening(MessageText, self)</boo>

Any received message will be handled by a function named OnMsgName, where Name is the name of the message class, without the Message prefix:

<boo> def OnMsgText(msg as MessageText):

       Debug.Log("Received a text message: ${msg.Text}")</boo>

Yes, naming is important. You should give all your message class names that start with Message.

Listening to a group of messages will be explained later in the Grouping messages section.

Defining messages

Basically all you have to do to define a message is to inherit from the Message class:

<boo>class MessageGamePaused (Message):

   pass</boo>

You can create a message with additional properties by adding properties to your message class. For example, you might want to attach a string:

<boo>class MessageText (Message):

   Text:
       get:
           return _text
   _text as string
   def constructor(text):
       _text = text
       # send the message
       super()</boo>

Make sure that the base class constructor is called last, since it actually sends the message.

Tip: Create a folder for all of your message scripts to keep your workspace organized.

Grouping messages

You can group your messages hierarchially using multiple levels of inheritance. For example, you can have a group of messages that have something in common:

<boo>class MessageBall (Message):

   pass</boo>

Inherit from it to create individual messages:

<boo>class MessageBallKicked (MessageBall):

   pass</boo>

<boo>class MessageBallInGoal (MessageBall):

   pass</boo>

<boo>class MessageBallOutOfBounds (MessageBall):

   pass</boo>

Easily subscribe to receive all of them:

<boo>import UnityEngine

class Referee (MonoBehaviour):

   def OnEnable():
       God.Inst.Hermes.Listen(MessageBall, self)
   def OnDisable():
       God.Inst.Hermes.StopListening(MessageBall, self)
   def OnMsgBallKicked(msg as MessageBallKicked):
       Debug.Log("Ball was kicked.")
   def OnMsgBallInGoal(msg as MessageBallInGoal):
       Debug.Log("Ball is in goal.")
   def OnMsgBallOutOfBounds(msg as MessageBallOutOfBounds):
       Debug.Log("Ball is out of bounds.")</boo>

In a similar fashion you can also listen to all messages by subscribing to the Message base class.

Performance

BooMessenger is quite efficient when it comes to speed. While it is not as fast as delegates, it is still faster than using SendMessage. The reason for this is that it does not iterate through all scripts attached to a game object. It keeps a direct reference to the listening script.

Still, it should be used moderately. It is ill-advised to use it inside Update or FixedUpdate.

Using with JavaScript or C#

The topic of scripting languages interoperability is already covered by Unity in: Script compilation. Simply place all the Boo code inside the "Standard Assets" folder (for example under "Standard Assets/Scripts/Misc") and leave the code that uses it outside.

Code

BooMessenger.boo

<boo>import UnityEngine

class BooMessenger:

   _listeners = {}
   """ Enlisted listeners. """
   def Listen(msgType as System.Type, listener as MonoBehaviour):
   """ Starts listening to messages derived from the specified type. """
   
       # verify type inherits from Message
       unless msgType.IsSubclassOf(Message) or msgType == Message:
           raise "Listened type is not a Message"
       # get list (create if necessary)
       if msgType not in _listeners:
           _listeners[msgType] = []
       list as List = _listeners[msgType]
       
       # add listener
       if listener not in list:
           list.Add(listener)
   
   def StopListening(msgType as System.Type, listener as MonoBehaviour):
   """ Stops listening to messages derived from the specified type. """
   
       # get list
       list as List = _listeners[msgType]
       return unless list
       # remove listener
       list.Remove(listener)
   def Send(msg as Message):
   """ Dispatches a message. """
       # send message (to listeners of base classes too)
       for msgType in msg.BaseClasses:
           # get list
           list = _listeners[msgType]
           continue unless list
           
           # send to all listeners
           for listener as MonoBehaviour in list:
               # invoke component method by name
               cb = listener.GetType().GetMethod(msg.FunctionName)
               if cb: cb.Invoke(listener, (msg,))
   </boo>

Message.boo

<boo>class Message:

   FunctionName:
   """ Method name in listener components """
       get:
           return _functionName
   _functionName as string
   BaseClasses:
   """ Inheritance route """
       get:
           return _baseClasses
   _baseClasses as (System.Type)
   def constructor():
   """ Creates and dispatches a message. """
       # replace 'Message' with 'OnMsg'
       _functionName = 'OnMsg' + self.GetType().ToString()[7:]
       _baseClasses = array(System.Type, GetBaseClasses())
       God.Inst.Hermes.Send(self)
   protected def GetBaseClasses():
   """ Generates inheritance route """
   
       msgType = self.GetType()
       while msgType != Message:
           yield msgType
           msgType = msgType.BaseType
       yield Message</boo>

God.boo

<boo>class God():

   static Inst as God:
   """ Calls upon God """
       get:
           God() unless _instance
           return _instance
   static _instance as God
   Hermes:
   """ The messenger """
       get:
           return _hermes
   _hermes as Messenger
   private def constructor():
   """ Wakes up God """
   
       _instance = self
       _hermes = Messenger()</boo>
Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox