Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members | Related Pages

dcouple Documentation

0.3

NOTE: Due to incomplete support for the D programming language in Doxygen, the documentation behind the links above is both incomplete and incorrect. If you want to be sure, consult the source code. This page contains manually generated documentation to make up for that.

Contents

  1. Introduction
  2. The Point of Management
  3. Connections
  4. Signals
    1. Blocking Signals
    2. Free Signals
  5. Slots
    1. Removing Duplicate Functionality in the API
    2. Free Slots (Experimental)
  6. Both Signals and Slots
  7. Extending Legacy Classes
  8. Examples
  9. Project Status
  10. Changelog

Introduction

The "signal and slot" mechanism is a type-safe alternative to using call-back functions through function pointers. It can be used to equip objects and program components with an interface through which the components can be tied together into systems that were not envisioned at the time of design of the components. The mechanism has its widest application in GUI programming. For further information see the file Resources.txt.

The Point of Management

Obviously a signal can be implemented as a class or struct with a container for delegates. Connections can then be made by adding delegates (pointers to member functions) to the container, and when the signal is called to emit, it calls all the member functions registered with the signal. The member functions are behaving like slots, and there is no distinction between slots and member functions. The connection can be made from any place where both the signal and the slot are in scope. Breaking (disconnecting) an existing connection happens by removing the appropriate delegate from the container in the appropriate signal, so the same information is needed as at the time when the connection was established. An implementation of this design is provided by Andy Friesen in listener.d.

The problem is, that when an object to which signals are connected is destructed, the connection must be broken in order to prevent an access violation. So slots need to be aware of existing connections just as well as signals are. The matter of disconnection and destruction is delicate in D, because the general rule is that no references to other objects may appear in destructors (see the specs) because finalisation is non-deterministic. In other words, the referred to objects may not exist any more, again resulting in an access violation.

Apart from gracefull destruction, there are other situations in which the receiving end may want to break a connection. One of them is that in D, objects that are not in use anymore will not be garbage collected if it still has connections to it, because they count as a reference. So you may want to give objects that contain signals and/or slots a method that shuts down all connections to and from it when you no longer need it.

In the following, we will cover different aspects of this implementation of managed signals and slots. We will start with making connections, because signals and slots are connected more often than they are defined.

Connections

Both Signals and Slots have a method called connect(), and it does not matter which one you call. So if there is a signal in okButton called pressed and a slot in areYouSure called yes, the following are equivalent:
okButton.pressed.connect(areYouSure.yes);
areYouSure.yes.connect(okButton.pressed);
Multiple identical connections may exist, which causes a slot to receive the same signal as many times per emission as connections were made. This is in accordance with the behaviour of Qt.

As they say at Trolltech: "syntax matters". We can mimmic the syntax for making a connection that Trolltech advocates. In fact our syntax is even less verbose than theirs, and thanks to the (limited) support for real-time type information (RTTI) in D, we can do without template functions. By not using a macro, our users need not bother about the order of arguments either. Again, the following are equivalent, to each other as well as to the above calls:

import dcouple.connect;

connect(okButton.pressed, areYouSure.yes);
connect(areYouSure.yes, okButton.pressed);

Signals and slots may have arguments, by which information can be transmitted between program components. When trying to establish a connection, the signal and slot must be compatible in the number and types of their arguments. When calling the connect() member function on incompatible signals and slots, compilation will not succeed. When calling the free connect() function this way, a run-time warning will be given, and no connection will be made. For example, if there is a counter with a signal called contentsChanged with an int argument, and areYouSure.yes has no arguments,

counter.contentsChanged.connect(areYouSure.yes);
will result in a compile error along the lines of

test.d(87): function connect (Slot s) does not match argument types (Slot )

and

connect(counter.contentsChanged, areYouSure.yes);
will complain during program execution like

dcouple WARNING: CounterWidget.Signal!(int) is incompatible with DialogWidget.Slot!(); not connected. (Sorry, no line number...)

The indication of argument mis-match works with build-in types as well as custom classes, at least in this run-time case. We would have liked to provide a file and line number and use object names in stead of class names, but currently this is the best we can do.

Connections can be removed by calling one of the disconnect() functions analogously. This is taken care of automatically when a signal or slot is destructed.

Signals

When a class is to be equipped with a signal, it is advised to make it inherit the SignalManager interface, and mix in its implementation.
class Widget : SignalManager
{
        mixin SignalManagement;
This provides better run-time warnings when an incompatible connection is attempted to be made, and, more importantly, allows the object to be deleted in a controlled way, including its signals.

The signal itself is defined by a templated member like so:

        Signal!(int) contentsChanged;
Naturally, it must be newed in the constructor.
        this() {
                contentsChanged = new Signal!(int)(this);
        }
By passing the this pointer in the signal constructor, the signal marks itself as being owned by the manager (Widget in this case) and registers itself with it.

The signal can then be emitted by calling

                /*...*/
                contentsChanged.emit(myVal);
                /*...*/
}

Blocking Signals

An object that is a SignalManager can temporarily be made to turn off its ordinary signals by means of the signalsBlocked(bool) property.
Widget w = new Widget;
/* Set connections etc. */
w.signalsBlocked = true;
contentsChanged.emit(myVal);    // The signal is not emitted.

For the rare occasion that a signal must ignore the block of its owner, it can be defined as a NonBlockingSignal. Such a signal can be used to emit a destroyed signal in the destructor, for example.

class Widget : SignalManager
{
        mixin SignalManagement;
        NonBlockingSignal!() destroyed;
        this()
        {
                destroyed = new NonBlockingSignal!(this);
        }
        ~this()
        {
                destroyed.emit();
                deleteSignals();        // See below.
        }
}

Note:
When you provide a destructor in a managing class, you are replacing the one that SignalManagement provides. You must therefore duplicate the contents from that destructor (with deleteSignals(); something like SM.~this(), where SM identifies the mixin, does not seem to work yet.).
Warning:
One should be careful to rely on signals in a destructor to be emitted. Garbage collected objects have no deterministic finalisation, and therefore the signal (destroyed in this case) may have been deleted before the owner gets deleted (Widget in this case). It will work as intended if you delete the owner explicitly however.

Free Signals

A signal does not need to be owned, for example if it lives outside a class.
Signal!() freeSignal = new Signal!()(null);

A free signal can not be blocked through its owner of course. Blocking signals individually is not supported yet.

Slots

Slots are managed as well.
class Widget : SlotManager
{
        mixin SlotManagement;
The importance of a managing owner is more apparent for slots than for signals. If a slot outlives its owner (due to the non-deterministic finalisation of garbage collected objects) they still receive signals and it has been shown that the member functions with which they are associated may still be operational despite the object having been destructed. SlotManagement takes care that connections are broken and that all relevant actors leave the scene at the right moment.

A slot is defined in the same way as signals are:

        Slot!(int) set;
The slot should be related to one of the member functions at the time of initialisation:
        this()
        {
                set = new Slot!(int)(this, &setVal);
        }
where setVal is the member function that you want to be called when a signal arrives,
        void setVal(int i) { my_int = i; }
private:
        int my_int;
}

The function reference on which a slot is created may be to any of the following

Warning:
It is important that the slot object does not outlive the function that it references. Although you will be able to construct a slot that references a non-static nested function, it is not safe to do so. A segmentation fault will be the result if a slot attempts to call a function that has been garbage collected. For the same reason, when constructing a slot on a member function, that slot must be owned by the object (implementing a SlotManager) that has the member function. The destructor of the owner will remove the slot before the member function becomes garbage.
When the nested function goes out of scope, it may be collected as garbage before the slot is destructed, which will result in a segmentation fault if the slot receives a signal at this moment.

Removing Duplicate Functionality in the API

In the above example, the class interface has two methods for accomplishing the same thing. Calling setVal(5) is equivalent to calling set(5). To simplify the API, setVal() may be made private.

Free Slots (Experimental)

Analogous to free signals, a slot can live outside a class.
void message()
{
        printf("You can turn a free function into a slot as well.\n");
}

void main()
{
        Slot!() freeSlot = new Slot!()(null,&message);
        Signal!() freeSig = new Signal!()(null);
        connect(freeSig, freeSlot);
        freeSig.emit();
}
As mentioned above, there is reason to be carefull with free slots, because their destruction is not linked with the life span of the function that they reference. It is best to disconnect free slots before their referenced function goes out of scope.

There is one scenario in which a segmentation fault will happen even when only static functions are referenced. Suppose a special signal has been implemented (inheriting from some Signal template) that is guaranteed to emit when its owner (or itself) is being destructed. When such a signal remains alive until the program terminates, it will emit during the final garbage collection. Connected slots that are still alive will receive the signal, and call their referenced functions. But, even static functions are being collected at this point, and may have been collected before the signals and slots. As a result, the program may terminate with a segmentation fault.

In the future, free slots may automatically register with a global static SlotManager. Before termination of the program, this manager may be called to disconnect all "free" slots, and thus prevent the segmentation fault.

Both Signals and Slots

Classes that own both signals and slots should inherit from SignalSlotManager and mix in the SignalSlotManagement implementation.

Currently, dcouple supports signals and slots with up to seven arguments. More can easily be added, although their practicality can be questioned.

Extending Legacy Classes

Note:
Currently there is a bug in dmd involving interfaces. The following will be possible in the future.
Courtecy the concepts of interfaces and mixins of the D programming language, this implementation has the interesting property that existing (probably third-party) classes can be equipped with signals and slots without interfering with their inheritance tree. Given the following legacy class
class ImportantClass : DeepInheritanceHierarchy
{
        /*...*/
        P importantProperty() { return _prop; }
        void importantProperty(P prop) { _prop = prop; }
private:
        P _prop;
}
A class like this can be extended in a straight forward manner, for example
class ExtendedClass : ImportantClass, SignalSlotManager
{
        mixin SignalSlotManagement;
        Signal!() propertyChanged;
        Slot!(P) updateProperty;
        this()
        {
                propertyChanged = new Signal!()(this);
                updateProperty = new Slot!(P)(this,&importantProperty);
        }
        void importantProperty(P prop)
        {
                super.importantProperty(prop);
                propertyChanged.emit();
        }
}

Without (working) interfaces, SignalSlotManager must be a class, and therefore positioned at the root of the inheritance tree.

Examples

More examples can be found in the examples directory.

Project Status

This framework is in early development. One of the aspects that have not been considered is thread-safety. Feedback is appreciated! Any volunteers to benchmark against other implementations?

Changelog


Generated on Mon Sep 12 22:10:39 2005 for dcouple by  doxygen 1.4.3