Signals and Functions

The basic buliding blocks of Dataflow.Signals communication are boost::signal, serving as a producer of signals, and boost::function, serving as a consumer of signals. A boost::signal can be permanently connected to anything that can be pointed to by a boost::function.

Here is a simple example that illustrates this:

#include <boost/dataflow/signals/support.hpp>
#include <iostream>
#include <string>

void consumer_function(const std::string &data)
{
    std::cout << data << std::endl;
}

void signal_function_example()
{
    boost::signal<void(const std::string &)> producer;
    boost::function<void(const std::string &)> consumer(consumer_function);
    
    connect(producer, consumer); // make a connection between the two.
    producer("Hello World"); // signal goes to the function.
}

If you are familiar with Boost.Signals, you will notice that the connect free function is something new - it is provided by Dataflow.Signals. Dataflow.Signals offers other utilities that make connection making easier, like the bind_mem_fn function which binds a member function to an object:

#include <boost/dataflow/utility/bind_mem_fn.hpp>

class consumer_class
{
public:
    void consumer_mem_fn(const std::string &data)
    {
        std::cout << data << std::endl;
    }
};

void signal_mem_fn_example()
{
    using boost::dataflow::utility::bind_mem_fn;
    
    boost::signal<void(const std::string &)> producer;
    consumer_class consumer;

    // make a connection between the producer and consumer.
    connect(producer, bind_mem_fn(&consumer_class::consumer_mem_fn, consumer));
    producer("Hello World"); // signal goes to the member function.
}

Components

In Dataflow.Signals, boost::signal and boost::function are considered ports (fundamental points of data production or consumption). Ports can be grouped together into components, which serve as fundamental data processing elements with one or more inputs and outputs (ports).

The typical incarnation of a component is a class, with one or more boost::signals as producer ports and any member function as a potential consumer port. While you can implement such classes any way you like, and use functionality described above to connect signals of one object to member functions of another, Dataflow.Signals offers some facilities that make things easier.

Here is a simple example that uses the filter and consumer classes, which serve as a foundation for Dataflow.Signals components. filter is best for components that have an output signal, and consumer for components that only have signal inputs.

#include <boost/dataflow/signals/component/filter.hpp>

using namespace boost;

// Inheriting from the filter class gives us a default output signal of
// a specified signature.
class producer_component
    : public signals::filter<producer_component, void(const std::string &)>
{
public:
    void invoke()
    {
        // out is the default output signal.
        out("Hello World");
    }
};

// Inheriting from signals::filter or signals::consumer allows us to implement
// signal consumer ports as overloads of operator() - later you will see that
// this approach allows us to access such ports more easily.
class consumer_component
    : public signals::consumer<consumer_component>
{
public:
    // This is our signal consumer.
    void operator()(const std::string &data)
    {
        std::cout << data << std::endl;
    }
};

void component_component_example()
{
    producer_component producer;
    consumer_component consumer;
    
    // Because we inherited from filter/consumer, connecting is easy.
    connect(producer, consumer);
    producer.invoke(); // producer sends "Hello World" to consumer.
};

Warning

At the moment, you can't use the connect function like it is used in the above example to connect to a const qualified operator() of a non-const qualified object (you will get a compile-time error). In this case, try casting the object to a const-qualified reference.

Chaining and Operators

The above components either produce (producer_component) or consume (consumer_component) signals. Let's see a filter example which does both - and at the same time let's introduce connection operators which make chaining of filters easier:

#include <boost/dataflow/signals/connection/operators.hpp>

class filter_component
    : public signals::filter<filter_component, void(const std::string &)>
{
public:
    // This is our signal consumer.  It will also produce a signal when called.
    void operator()(const std::string &data)
    {
        out(data + "!");
    }
};

void component_component_component_example()
{
    producer_component producer;
    filter_component filter;
    consumer_component consumer;
    
    // The following is equivalent to:
    // connect(producer, filter);
    // connect(filter, consumer);
    producer >>= filter >>= consumer;
    // producer sends "Hello World" to filter, filter sends "Hello World!".
    producer.invoke();
}

Provided Components

Dataflow.Signals provides many common building block components for a signal-based dataflow network. The most basic is storage, which can be used to store a value received through a signal, send it on, and/or retrieve it.

For example, when a storage object receives a signal through its operator() (i.e., its operator() gets called), it will store the value of the arguments, and depending on its state it might forward the signal onward. When the storage object's send() function is called (or the object is invoked via the invoke function), the stored values will be sent to any connected consumers.

The value stored inside a storage object can be retrieved via the at() method.

Using a few storage objects, it is easy to create a simple network:

// instantiate all of the components we need
signals::storage<void ()> banger;
signals::storage<void (float)> floater(2.5f);
signals::storage<void (float)> collector(0.0f);
        
// ---Connect the dataflow network -----------------------------
//
// ,--------.  void()   
// | banger | -------. 
// `--------'        |
//                   |
//                   V
//            ,-(send_slot)-.  void(float)   ,-----------.
//            |   floater   | -------------> | collector |
//            `-------------'                `-----------'
//
// -------------------------------------------------------------

banger >>= floater.send_slot();
floater >>= collector;

// signal from banger is will invoke floater.send(), which causes
// floater to output 2.5
banger();
BOOST_CHECK_EQUAL(floater.at<0>(), 2.5f);
BOOST_CHECK_EQUAL(collector.at<0>(), 2.5f);

floater.close();
floater(1.5f); // change the value in floater
// we can also signal floater directly, which will again cause it to 
// output its stored value:
invoke(floater); 
BOOST_CHECK_EQUAL(collector.at<0>(), 1.5f);

To proceed, you may want to look at