libcppa
Version 0.1
|
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.
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);
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 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); } ); }
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 }); // ...
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")); } );