libcppa  Version 0.1
Message handling.

This is the beating heart of libcppa. Actor programming is all about message handling.

A message in libcppa is a n-tuple of values (with size >= 1). You can use almost every type in a messages.

Send messages

The function send could be used to send a message to an actor. The first argument is the receiver of the message followed by any number of values. send creates a tuple from the given values and enqueues the tuple to the receivers mailbox. Thus, send should not be used to send a message to multiple receivers. You should use the enqueue member function of the receivers instead as in the following example:

 // spawn some actors
 auto a1 = spawn(...);
 auto a2 = spawn(...);
 auto a3 = spawn(...);

 // send a message to a1
 send(a1, atom("hello"), "hello a1!");

 // send a message to a1, a2 and a3
 auto msg = make_tuple(atom("compute"), 1, 2, 3);
 auto s = self(); // cache self() pointer
 // note: this is more efficient then using send() three times because
 //       send() would create a new tuple each time;
 //       this safes both time and memory thanks to libcppa's copy-on-write
 a1->enqueue(s, msg);
 a2->enqueue(s, msg);
 a3->enqueue(s, msg);

 // modify msg and send it again
 // (msg becomes detached due to copy-on-write optimization)
 get_ref<1>(msg) = 10; // msg is now { atom("compute"), 10, 2, 3 }
 a1->enqueue(s, msg);
 a2->enqueue(s, msg);
 a3->enqueue(s, msg);

Receive messages

The function receive takes a behavior as argument. The behavior is a list of { pattern >> callback } rules.

 receive
 (
     on<atom("hello"), std::string>() >> [](const std::string& msg)
     {
         cout << "received hello message: " << msg << endl;
     },
     on<atom("compute"), int, int, int>() >> [](int i0, int i1, int i2)
     {
         // send our result back to the sender of this messages
         reply(atom("result"), i0 + i1 + i2);
     }
 );

The function on creates a pattern. It provides two ways of defining patterns: either by template parameters (prefixed by up to four atoms) or by arguments. The first way matches for types only (exept for the prefixing atoms). The second way compares values. Use the template function val to match for the type only.

This example is equivalent to the previous one but uses the second way to define patterns:

 receive
 (
     on(atom("hello"), val<std::string>()) >> [](const std::string& msg)
     {
         cout << "received hello message: " << msg << endl;
     },
     on(atom("compute"), val<int>(), val<int>(), val<int>()>() >> [](int i0, int i1, int i2)
     {
         // send our result back to the sender of this messages
         reply(atom("result"), i0 + i1 + i2);
     }
 );

Atoms

Atoms are a nice way to add semantic informations to a message. Assuming an actor wants to provide a "math sevice" for integers. It could provide operations such as addition, subtraction, etc. This operations all have two operands. Thus, the actor does not know what operation the sender of a message wanted by receiving just two integers.

Example actor:

 void math_actor()
 {
     receive_loop
     (
         on<atom("plus"), int, int>() >> [](int a, int b)
         {
             reply(atom("result"), a + b);
         },
         on<atom("minus"), int, int>() >> [](int a, int b)
         {
             reply(atom("result"), a - b);
         }
     );
 }

Receive loops

Previous examples using receive create behavior on-the-fly. This is inefficient in a loop since the argument passed to receive is created in each iteration again. Its possible to store the behavior in a variable and pass that variable to receive. This fixes the issue of re-creation each iteration but rips apart definition and usage.

There are three convenience function implementing receive loops to declare patterns and behavior where they belong without unnecessary copies: receive_loop, receive_while and do_receive.

receive_loop is analogous to receive and loops "forever" (until the actor finishes execution).

receive_while creates a functor evaluating a lambda expression. The loop continues until the given returns false. A simple example:

 // receive two ints
 vector<int> received_values;
 receive_while([&]() { return received_values.size() < 2; })
 (
     on<int>() >> [](int value)
     {
         received_values.push_back(value);
     }
 );
 // ...

do_receive returns a functor providing the function until that takes a lambda expression. The loop continues until the given lambda returns true. Example:

 // receive ints until zero was received
 vector<int> received_values;
 do_receive
 (
     on<int>() >> [](int value)
     {
         received_values.push_back(value);
     }
 )
 .until([&]() { return received_values.back() == 0 });
 // ...

Send delayed messages

The function future_send provides a simple way to delay a message. This is particularly useful for recurring events, e.g., periodical polling. Usage example:

 future_send(self(), std::chrono::seconds(1), atom("poll"));
 receive_loop
 (
     // ...
     on<atom("poll")>() >> []()
     {
         // ... poll something ...
         // and do it again after 1sec
         future_send(self(), std::chrono::seconds(1), atom("poll"));
     }
 );