Sidebar: Senders and Receivers


Back to
lesson 3

Introduction

This sidebar gives some additional details on the TSender and TReceiver classes that are behind menu and idle events. This information isn't strictly necessary for writing programs based on GLOW, but is probably helpful for understanding what's going on behind the scenes.

TSender and TReceiver implement a technique commonly known as a broadcaster/listener system. In such a system, a broadcaster object may be connected, or bound, to one or more listener objects. At any time, the broadcaster may be instructed to broadcast a message object to all of its listeners. This causes some method on each listener to be invoked with that message as a parameter-- in this case, the method is OnMessage(). Such a system is commonly used for reporting events in an object-oriented model.

The classes TSender and TReceiver are actually templates; they are templated on the type of message they expect to send or receive. This allows compile-time typechecking. A sender may be bound only to receivers templated on the same type, and only messages of that type may be sent through that sender.

TSender and TReceiver also automatically sever connections when one of the endpoints is destructed. That is, if a receiver is deleted, it automatically notifies any senders connected to it, and those senders will remove it from their connection lists. This is a common service provided by most broadcaster/listener systems.

GLOW uses senders and receivers to report most high-level events, including menu events, idle events, timer events, and widget-related events like button presses. To receive these events, you'll need to subclass the appropriate receiver class and implement its OnMessage method. You may also use TSender and TReceiver directly to handle some of your own inter-object communication needs, as described in the next section.

Using senders and receivers

To use senders and receivers in your application, first you need to compile glowSenderReceiver.cpp, and include glowSenderReceiver.h in your source file. Next decide on a type to use as a message. If you use an aggregate type like a class, it is probably a good idea to send a pointer or a reference to it as a message, rather than its entire value. Let's suppose you have a struct MyMessageType, and you're going to send a reference as the message type.

struct MyMessageType {
    int data1;
    std::string data2;
};

Writing a receiver

First, you need to write a receiver class for your message. Subclass TReceiver<MyMessageType&>, and implement the method OnMessage(MyMessageType&). This method will be called whenever the receiver receives a message. Notice that, since we decided to use a non-const reference as our message type, the receiver can modify the message data.

class MyReceiver : public TReceiver<MyMessageType&>{
    virtual void OnMessage(MyMessageType&);
};

void MyReceiver::OnMessage(MyMessageType& message) {
    std::cout << "Received message" << std::endl;
    std::cout << " data1 = " << message.data1 << std::endl;
    std::cout << " data2 = " << message.data2 << std::endl;
    ++message.data1;
}

Creating a network of senders and receivers

Next, you need to create sender and receiver objects. Just make an object of type TSender<MyMessageType&> and an object of type MyReceiver. You can also make a class that inherits from TSender<MyMessageType&>.

TSender<MyMessageType&> mysender;
MyReceiver myreceiver;

Before you can send messages, you need to bind the sender to the receiver. To do this, call the Bind() method on your sender, and give it a pointer to the receiver. You can also sever the connection by calling the Unbind() method.

mysender.Bind(&myreceiver);

Sending messages

To actually send a message, just call the Send() method on the sender, and give it the message. For example,

MyMessage message;
message.data1 = 100;
message.data2 = "Hello, world!";
mysender.Send(message);

After sending the message, if we query the value of message.data1, we'll find that it has been incremented by the receiver. You can also bind a sender to any number of receivers. For example, let's create two more receivers and bind the sender to them. The order in which the receivers receive the message you send is the same order in which the receivers were bound.

class MyOtherReceiver : public TReceiver<MyMessageType&>{
    virtual void OnMessage(MyMessageType&);
};

void MyOtherReceiver::OnMessage(MyMessageType& message) {
    std::cout << "Received message with other" << std::endl;
    std::cout << " data1 = " << message.data1 << std::endl;
    std::cout << " data2 = " << message.data2 << std::endl;
    --message.data1;
}

MyOtherReceiver myreceiver2;
MyReceiver* myreceiver3ptr = new MyReceiver;
mysender.Bind(&myreceiver2);
mysender.Bind(myreceiver3ptr);
message.data1 = 100;
message.data2 = "Hello, world!";
mysender.Send(message);
std::cout << "data1 is now " << message.data1 << std::endl;

Running this should cause the receivers to receive the message in the order myreceiver, myreceiver2, myreceiver3ptr. This should generate this output:

Received message
 data1 = 100
 data2 = Hello, world!
Received message with other
 data1 = 101
 data2 = Hello, world!
Received message
 data1 = 100
 data2 = Hello, world!
data1 is now 101

Additional features

There are several other methods you can use to manage connections. The IsBoundTo() method of TSender lets you query whether the sender is bound to a certain receiver. NumReceivers() returns the number of receivers a sender is bound to. UnbindAll() severs all connections with this sender. Calling NumSenders() on a receiver will return the number of senders bound to that receiver.

Senders also automatically detect if their receivers are deleted. If you delete myreceiver3ptr;, then calling mysender.NumReceivers() will return 2.

You may also send a message to a single receiver without explicitly binding. To do this, use the static version of the Send() method, as in:

TSender<MyMessage&>::Send(myreceiver3ptr, message);

Back to
lesson 3


The GLOW Toolkit