BooMessenger

From Unify Community Wiki
(Difference between revisions)
Jump to: navigation, search
(Listening to messages)
(allowing listener component to disable itself.)
 
(24 intermediate revisions by 3 users not shown)
Line 5: Line 5:
 
[[Category:Reflection]]
 
[[Category:Reflection]]
 
[[Category:Boo]]
 
[[Category:Boo]]
Author: Ofer Reichman (tortoise)
+
Author: Ofer Reichman ([[User:Tortoise|tortoise]])
  
 
== Description ==
 
== Description ==
Line 12: Line 12:
 
== Usage ==
 
== Usage ==
 
=== Creating and using the messenger ===
 
=== Creating and using the messenger ===
Creating the messenger is easy. Simply instantiate it:
+
You don't need to create the messenger. It is created implicitly by the <i>God</i> class, which is bundled and implements the singleton pattern. To access the single messenger simply use:
  
<boo>        messenger = BooMessenger()</boo>
+
<syntaxhighlight lang="boo">        God.Inst.Hermes</syntaxhighlight>
  
However, you might want to keep 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 <i>God</i> class was bundled for your convenience. It allows easy access to the messenger from anywhere using:
+
The singleton mechanics were decoupled to provide a single place for all single instances.
 
+
<p></p>
<boo>       God.Inst.Hermes</boo>
+
Tip: Feel free to use the <i>God</i> class as a container for any classes of yours of which you only need one instance.
 
+
Feel free to develop however you like. If you don't use the <i>God</i> class, however, mind that you change the appropriate line in the <i>Message</i> class.
+
  
 
=== Dispatching a message ===
 
=== Dispatching a message ===
To create and send a message simply instantiate a message class, provided with parameters, and voila!
+
To create and send a message simply instantiate a message class, provided with parameters, and voila! For example, if you have a message called <i>MessageText</i> you can write:
  
<boo>        MessageText("Hello!")</boo>
+
<syntaxhighlight lang="boo">        MessageText("Hello!")</syntaxhighlight>
  
 
The message will be sent right away.
 
The message will be sent right away.
  
 
=== Listening to messages ===
 
=== Listening to messages ===
You can subscribe to receive a message or a group of message using the <i>Listen</i> function. It receives the message type and the listener, which is the component calling (<i>self</i>):
+
You can subscribe to receive a message or a group of messages using the <i>Listen</i> function. It receives the message type and the listener, which is the component calling (<i>self</i>):
  
<boo>    def OnEnable():
+
<syntaxhighlight lang="boo">    def OnEnable ():
         God.Inst.Hermes.Listen(MessageText, self)</boo>
+
         God.Inst.Hermes.Listen(MessageText, self)</syntaxhighlight>
  
 
Note: The listening script must be attached to a game object.
 
Note: The listening script must be attached to a game object.
Line 39: Line 37:
 
You can unsubscribe using the <i>StopListening</i> function. It receives the same parameters:
 
You can unsubscribe using the <i>StopListening</i> function. It receives the same parameters:
  
<boo>    def OnDisable():
+
<syntaxhighlight lang="boo">    def OnDisable ():
         God.Inst.Hermes.StopListening(MessageText, self)</boo>
+
         God.Inst.Hermes.StopListening(MessageText, self)</syntaxhighlight>
  
 
Any received message will be handled by a function named <i>OnMsgName</i>, where <i>Name</i> is the name of the message class, without the <i>Message</i> prefix:
 
Any received message will be handled by a function named <i>OnMsgName</i>, where <i>Name</i> is the name of the message class, without the <i>Message</i> prefix:
  
<boo>    def OnMsgText(msg as MessageText):
+
<syntaxhighlight lang="boo">    def OnMsgText (msg as MessageText):
         Debug.Log("Received a text message: ${msg.Text}")</boo>
+
         Debug.Log("Received a text message: ${msg.Text}")</syntaxhighlight>
  
Yes, naming is important. You should give all your message class names that start with <i>Message</i>.
+
<b>Yes, naming is important.</b> You should give all your message class names that start with <i>Message</i>.
 
<p></p>
 
<p></p>
 
Listening to a group of messages will be explained later in the <i>Grouping messages</i> section.
 
Listening to a group of messages will be explained later in the <i>Grouping messages</i> section.
  
 
=== Defining messages ===
 
=== Defining messages ===
Basically all you have to do to define a message is to inherit from the <i>Message</i> class:
+
Messages are defined by inheriting from the <i>Message</i> class:
  
<boo>class MessageGamePaused(Message):
+
<syntaxhighlight lang="boo">class MessageGamePaused (Message):
     pass</boo>
+
     pass</syntaxhighlight>
  
You can create a message with additional properties by adding properties to your message class. For example, you might want to attach a string:
+
You can define 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):
+
<syntaxhighlight lang="boo">class MessageText (Message):
  
 
     Text:
 
     Text:
Line 66: Line 64:
 
     _text as string
 
     _text as string
  
     def constructor(text):
+
     def constructor (text):
  
 
         _text = text
 
         _text = text
  
 
         # send the message
 
         # send the message
         super()</boo>
+
         super()</syntaxhighlight>
  
 
Make sure that the base class constructor is called last, since it actually sends the message.
 
Make sure that the base class constructor is called last, since it actually sends the message.
Line 78: Line 76:
  
 
=== Grouping messages ===
 
=== Grouping messages ===
You can group your messages hierarchially using multiple levels of inheritance. For example, you might have a group of messages that have something in common:
+
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):
+
<syntaxhighlight lang="boo">class MessageBall (Message):
     pass</boo>
+
     pass</syntaxhighlight>
  
 
Inherit from it to create individual messages:
 
Inherit from it to create individual messages:
  
<boo>class MessageBallKicked(MessageBall):
+
<syntaxhighlight lang="boo">class MessageBallKicked (MessageBall):
     pass</boo>
+
     pass</syntaxhighlight>
<boo>class MessageBallInGoal(MessageBall):
+
<syntaxhighlight lang="boo">class MessageBallInGoal (MessageBall):
     pass</boo>
+
     pass</syntaxhighlight>
<boo>class MessageBallOutOfBounds(MessageBall):
+
<syntaxhighlight lang="boo">class MessageBallOutOfBounds (MessageBall):
     pass</boo>
+
     pass</syntaxhighlight>
  
 
Easily subscribe to receive all of them:
 
Easily subscribe to receive all of them:
  
<boo>import UnityEngine
+
<syntaxhighlight lang="boo">import UnityEngine
  
 
class Referee (MonoBehaviour):  
 
class Referee (MonoBehaviour):  
  
     def OnEnable():
+
     def OnEnable ():
 
         God.Inst.Hermes.Listen(MessageBall, self)
 
         God.Inst.Hermes.Listen(MessageBall, self)
  
     def OnDisable():
+
     def OnDisable ():
 
         God.Inst.Hermes.StopListening(MessageBall, self)
 
         God.Inst.Hermes.StopListening(MessageBall, self)
  
     def OnMsgBallKicked(msg as MessageBallKicked):
+
     def OnMsgBallKicked (msg as MessageBallKicked):
 
         Debug.Log("Ball was kicked.")
 
         Debug.Log("Ball was kicked.")
  
     def OnMsgBallInGoal(msg as MessageBallInGoal):
+
     def OnMsgBallInGoal (msg as MessageBallInGoal):
 
         Debug.Log("Ball is in goal.")
 
         Debug.Log("Ball is in goal.")
  
     def OnMsgBallOutOfBounds(msg as MessageBallOutOfBounds):
+
     def OnMsgBallOutOfBounds (msg as MessageBallOutOfBounds):
         Debug.Log("Ball is out of bounds.")</boo>
+
         Debug.Log("Ball is out of bounds.")</syntaxhighlight>
  
 
In a similar fashion you can also listen to <b>all</b> messages by subscribing to the <i>Message</i> base class.
 
In a similar fashion you can also listen to <b>all</b> messages by subscribing to the <i>Message</i> base class.
  
 
== Performance ==
 
== Performance ==
BooMessenger is quite efficient when it comes to speed. While it is not as fast as delegates, it is still faster than using [http://unity3d.com/support/documentation/ScriptReference/GameObject.SendMessage.html 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.
+
BooMessenger is quite efficient when it comes to speed. While it is not as fast as delegates, it is slightly faster than using [http://unity3d.com/support/documentation/ScriptReference/GameObject.SendMessage.html 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.
 
<p></p>
 
<p></p>
 
Still, it should be used moderately. It is ill-advised to use it inside [http://unity3d.com/support/documentation/ScriptReference/MonoBehaviour.Update.html Update] or [http://unity3d.com/support/documentation/ScriptReference/MonoBehaviour.FixedUpdate.html FixedUpdate].
 
Still, it should be used moderately. It is ill-advised to use it inside [http://unity3d.com/support/documentation/ScriptReference/MonoBehaviour.Update.html Update] or [http://unity3d.com/support/documentation/ScriptReference/MonoBehaviour.FixedUpdate.html FixedUpdate].
  
== Using with JavaScript or C# ==
+
== Integrating with JavaScript/C# ==
The topic of scripting languages interoperability is already covered by Unity in: [http://unity3d.com/support/documentation/ScriptReference/index.Script_compilation_28Advanced29.html 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.
+
It is possible to use BooMessenger together with any JavaScript/C# scripts you may have. The topic of scripting languages interoperability is already covered by Unity in: [http://unity3d.com/support/documentation/ScriptReference/index.Script_compilation_28Advanced29.html Script compilation]. Simply place all the Boo code inside the "Standard Assets" folder (for example under "Standard Assets/Scripts/Messenger") and leave the code that uses it outside.
  
 
== Code ==
 
== Code ==
 
=== BooMessenger.boo ===
 
=== BooMessenger.boo ===
<boo>import UnityEngine
+
<syntaxhighlight lang="boo">import UnityEngine
  
 
class BooMessenger:
 
class BooMessenger:
Line 132: Line 130:
 
     """ Enlisted listeners. """
 
     """ Enlisted listeners. """
  
     def Listen(msgType as System.Type, listener as MonoBehaviour):
+
     def Listen (msgType as System.Type, listener as MonoBehaviour):
 
     """ Starts listening to messages derived from the specified type. """
 
     """ Starts listening to messages derived from the specified type. """
 
      
 
      
Line 148: Line 146:
 
             list.Add(listener)
 
             list.Add(listener)
 
      
 
      
     def StopListening(msgType as System.Type, listener as MonoBehaviour):
+
     def StopListening (msgType as System.Type, listener as MonoBehaviour):
 
     """ Stops listening to messages derived from the specified type. """
 
     """ Stops listening to messages derived from the specified type. """
 
      
 
      
Line 158: Line 156:
 
         list.Remove(listener)
 
         list.Remove(listener)
  
     def Send(msg as Message):
+
     def Send (msg as Message):
 
     """ Dispatches a message. """
 
     """ Dispatches a message. """
  
Line 168: Line 166:
 
              
 
              
 
             # send to all listeners
 
             # send to all listeners
             for listener as MonoBehaviour in list:
+
             for i in range(len(list) - 1, -1, -1):
 +
                listener as MonoBehaviour = list[i]
 
                 # invoke component method by name
 
                 # invoke component method by name
 
                 cb = listener.GetType().GetMethod(msg.FunctionName)
 
                 cb = listener.GetType().GetMethod(msg.FunctionName)
 
                 if cb: cb.Invoke(listener, (msg,))
 
                 if cb: cb.Invoke(listener, (msg,))
     </boo>
+
     </syntaxhighlight>
  
 
=== Message.boo ===
 
=== Message.boo ===
  
<boo>class Message:
+
<syntaxhighlight lang="boo">class Message:
  
 
     FunctionName:
 
     FunctionName:
Line 190: Line 189:
 
     _baseClasses as (System.Type)
 
     _baseClasses as (System.Type)
  
     def constructor():
+
     def constructor ():
 
     """ Creates and dispatches a message. """
 
     """ Creates and dispatches a message. """
  
Line 200: Line 199:
 
         God.Inst.Hermes.Send(self)
 
         God.Inst.Hermes.Send(self)
  
     protected def GetBaseClasses():
+
     protected def GetBaseClasses ():
 
     """ Generates inheritance route """
 
     """ Generates inheritance route """
 
      
 
      
Line 207: Line 206:
 
             yield msgType
 
             yield msgType
 
             msgType = msgType.BaseType
 
             msgType = msgType.BaseType
         yield Message</boo>
+
         yield Message</syntaxhighlight>
  
 
=== God.boo ===
 
=== God.boo ===
<boo>class God():
+
<syntaxhighlight lang="boo">class God:
  
 
     static Inst as God:
 
     static Inst as God:
Line 223: Line 222:
 
         get:
 
         get:
 
             return _hermes
 
             return _hermes
     _hermes as Messenger
+
     _hermes as BooMessenger
  
     private def constructor():
+
     private def constructor ():
 
     """ Wakes up God """
 
     """ Wakes up God """
 
      
 
      
 
         _instance = self
 
         _instance = self
         _hermes = Messenger()</boo>
+
         _hermes = BooMessenger()</syntaxhighlight>

Latest revision as of 07:50, 1 March 2013

Author: Ofer Reichman (tortoise)

Contents

[edit] 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.

[edit] Usage

[edit] Creating and using the messenger

You don't need to create the messenger. It is created implicitly by the God class, which is bundled and implements the singleton pattern. To access the single messenger simply use:

        God.Inst.Hermes

The singleton mechanics were decoupled to provide a single place for all single instances.

Tip: Feel free to use the God class as a container for any classes of yours of which you only need one instance.

[edit] Dispatching a message

To create and send a message simply instantiate a message class, provided with parameters, and voila! For example, if you have a message called MessageText you can write:

        MessageText("Hello!")

The message will be sent right away.

[edit] 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):

    def OnEnable ():
        God.Inst.Hermes.Listen(MessageText, self)

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

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

    def OnDisable ():
        God.Inst.Hermes.StopListening(MessageText, self)

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

    def OnMsgText (msg as MessageText):
        Debug.Log("Received a text message: ${msg.Text}")

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.

[edit] Defining messages

Messages are defined by inheriting from the Message class:

class MessageGamePaused (Message):
    pass

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

class MessageText (Message):
 
    Text:
        get:
            return _text
    _text as string
 
    def constructor (text):
 
        _text = text
 
        # send the message
        super()

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.

[edit] 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:

class MessageBall (Message):
    pass

Inherit from it to create individual messages:

class MessageBallKicked (MessageBall):
    pass
class MessageBallInGoal (MessageBall):
    pass
class MessageBallOutOfBounds (MessageBall):
    pass

Easily subscribe to receive all of them:

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.")

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

[edit] Performance

BooMessenger is quite efficient when it comes to speed. While it is not as fast as delegates, it is slightly 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.

[edit] Integrating with JavaScript/C#

It is possible to use BooMessenger together with any JavaScript/C# scripts you may have. 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/Messenger") and leave the code that uses it outside.

[edit] Code

[edit] BooMessenger.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 i in range(len(list) - 1, -1, -1):
                listener as MonoBehaviour = list[i]
                # invoke component method by name
                cb = listener.GetType().GetMethod(msg.FunctionName)
                if cb: cb.Invoke(listener, (msg,))

[edit] Message.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

[edit] God.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 BooMessenger
 
    private def constructor ():
    """ Wakes up God """
 
        _instance = self
        _hermes = BooMessenger()
Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox