Developer's Guide: Message Passing

User Interface | Developer's Guide | File Format

# Message Passing

In a Model-View-Controller architecture like the one used in Skencil, the model has to inform the views when the data has changed and the views have to be updated. Often there can be several views for one model object and it is desirable to implement the model in such a way that it doesn't have to know how many and what types of view there actually are at any one point in time. Still, the views have to be informed by the model that they should update themselves whenever the model changes. In Skencil this is achieved through a generic message passing mechanism similar to the signals used in Qt or GTK.

## Concepts

Messages are generated by publishers and consist of a message type represented by a string and an arbitrary number of arguments. The message type is also called a channel.

Program parts that want to receive messages subscribe to a channel of a particular publisher by passing a callable object, often a bound instance method, and optionally some additional arguments to the publisher.

When the publisher issues a message all subscribers of the relevat channel of the publisher all called with the message arguments and the additional subscriber specific arguments.

The heart of this mechanism is the `Connector` class, whose instances manage all connections between publishers and subscribers. This way, a publisher that has no subscribers does not use any resources.

The connector module has one global `Connector` instance and some of its methods are available as globals.

There are two additional classes for publisher objects. `Publisher` provides methods to subscribe to and unsubscribe from channels of the object. `QueueingPublisher` allows the publisher to accumulate messages in a queue with identical messages queued only once and release them all at once.

Most objects that send messages are derived from one of these publisher objects and in most circumstances their methods are used to connect to their channels. The connector is rarely used directly.

## The Class `Connector`

`Connect(publisher, channel, subscriber, args)`

  Connect the subscriber, i.e. a callable object and additional arguments args, to a channel of a publisher.

  The publisher can be any object, though most of the time it's a `Publisher` instance. Skencil uses the `None` object as a publisher for global messages.

  args are subscriber specific arguments as a tuple.

  The same subscriber may be registered multiple times but it is only stored once in the list of subscribers. Two subscribers are considered equal if the callable object and the arguments are equal.

`Disconnect(publisher, channel, subscriber, args)`

  Disconnect subscriber from publisher's channel.

  The arguments have the same meaning as in `Connect` and again the combination of subscriber and args is used to identify one particular subscriber.

  If the subscriber isn't connected to the channel, raise a `ConnectorError` exception.

`Issue(publisher, channel, *args)`

  Issue a message on a channel of publisher.

  The third and all following arguments are passed through to all subscribers as message arguments. The arguments the subscribers are called with is the concatenation of the message arguments and the subscriber specific arguments.

`RemovePublisher(publisher)`

  Remove all subscribers from all channels of publisher. It is safe to call this in a __del__ method.

`HasSubscribers(publisher)`

  Return true iff publisher has any subscribers.

Note that the connector does not hold references to the publisher. Publishers are identified by their id. This makes it possible to automatically disconnect all subscribers of a publisher in a publisher's __del__ method. This does not work however, if e.g. the publisher is itself a subscriber because the connector has to own the references to the subcribers.

## The Class `Publisher`

The class `Publisher` serves as a base class for classes that send messages. It provides convenience methods to subscribe to and unsubscribe from channels. It also removes all subscriptions automatically when instances are deleted through the `__del__` method.

`Subscribe(channel, subscriber, *args)`

  Subscribe subscriber to self's channel with the rest of the positional arguments as the subscriber specific parameters. See `Connect` for more details.

`Unsubscribe(channel, subscriber, *args)`

  Unsubscribe subscriber to self's channel with the rest of the positional arguments as the subscriber specific parameters. See `Disconnect` for more details.

`issue(channel, *args)`

  If the instance variable `ignore_issue` is false, issue the message described by the second and the following arguments on channel channel, otherwise, do nothing.

  `ignore_issue` defaults to false. `ignore_issue` will be removed in 0.8.

`Destroy()`

  Remove all subscribers of all of self's channels. See `RemovePublisher` for more details.

`__del__()`

  Remove all subscribers of all of self's channels. See `RemovePublisher` for more details.

## The Class `QueueingPublisher`

Sometimes it's useful to put messages in a queue and issue them all in one go after a complex operation is finished. The class `QueueingPublisher` provides such a queue and the following methods:

`queue_message(channel, *args)`

  Put message for channel channel in the queue. The rest of the arguments are treated just as in `issue`

  If the messge is already queued remove it and put it at the end. This is done to make certain that no channel gets called twice between two calls to flush_message_queue. If the order of channel invocation is important two or more queues should be used.

  Note, that this can be very inefficient if a lot of different messages are queued before the queue is flushed because each new message is compared with all messages in the queue sequentiually. A dictionary could speed this up considerably if the order in which the messages are issued isn't important and if the messages can be used as dictionary keys. Especially the latter is not always the case, so this isn't done by defalt.

`flush_message_queue()`

  Issue all queued messages until the queue is empty. The process is repeated because issueing messages might result in new messages being queued. This can theoretically result in an infinite loop.

`clear_message_queue()`

  Discard all queued messages.

User Interface | Developer's Guide | File Format