103 lines
3.7 KiB
ReStructuredText
103 lines
3.7 KiB
ReStructuredText
.. _message-handler:
|
|
|
|
Message Handlers
|
|
================
|
|
|
|
Actors can store a set of callbacks---usually implemented as lambda
|
|
expressions---using either ``behavior`` or ``message_handler``.
|
|
The former stores an optional timeout, while the latter is composable.
|
|
|
|
Definition and Composition
|
|
--------------------------
|
|
|
|
As the name implies, a ``behavior`` defines the response of an actor to
|
|
messages it receives. The optional timeout allows an actor to dynamically
|
|
change its behavior when not receiving message after a certain amount of time.
|
|
|
|
.. code-block:: C++
|
|
|
|
message_handler x1{
|
|
[](int32_t i) { /*...*/ },
|
|
[](double db) { /*...*/ },
|
|
[](int32_t a, int32_t b, int32_t c) { /*...*/ }
|
|
};
|
|
|
|
In our first example, ``x1`` models a behavior accepting messages that consist
|
|
of either exactly one ``int``, or one ``double``, or three ``int`` values. Any
|
|
other message is not matched and gets forwarded to the default handler (see
|
|
:ref:`default-handler`).
|
|
|
|
.. code-block:: C++
|
|
|
|
message_handler x2{
|
|
[](double db) { /*...*/ },
|
|
[](double db) { /* - unreachable - */ }
|
|
};
|
|
|
|
Our second example illustrates an important characteristic of the matching
|
|
mechanism. Each message is matched against the callbacks in the order they are
|
|
defined. The algorithm stops at the first match. Hence, the second callback in
|
|
``x2`` is unreachable.
|
|
|
|
.. code-block:: C++
|
|
|
|
message_handler x3 = x1.or_else(x2);
|
|
message_handler x4 = x2.or_else(x1);
|
|
|
|
Message handlers can be combined using ``or_else``. This composition is
|
|
not commutative, as our third examples illustrates. The resulting message
|
|
handler will first try to handle a message using the left-hand operand and will
|
|
fall back to the right-hand operand if the former did not match. Thus,
|
|
``x3`` behaves exactly like ``x1``. This is because the second
|
|
callback in ``x1`` will consume any message with a single
|
|
``double`` and both callbacks in ``x2`` are thus unreachable.
|
|
The handler ``x4`` will consume messages with a single
|
|
``double`` using the first callback in ``x2``, essentially
|
|
overriding the second callback in ``x1``.
|
|
|
|
.. _atom:
|
|
|
|
Atoms
|
|
-----
|
|
|
|
Defining message handlers in terms of callbacks is convenient, but requires a
|
|
simple way to annotate messages with meta data. Imagine an actor that provides
|
|
a mathematical service for integers. It receives two integers, performs a
|
|
user-defined operation and returns the result. Without additional context, the
|
|
actor cannot decide whether it should multiply or add the integers. Thus, the
|
|
operation must be encoded into the message. The Erlang programming language
|
|
introduced an approach to use non-numerical constants, so-called
|
|
*atoms*, which have an unambiguous, special-purpose type and do not have
|
|
the runtime overhead of string constants.
|
|
|
|
Atoms in CAF are tag types, i.e., usually defined as en empty ``struct``. These
|
|
types carry no data on their own and only exist to annotate messages. For
|
|
example, we could create the two tag types ``add_atom`` and ``multiply_atom``
|
|
for implementing a simple math actor like this:
|
|
|
|
.. code-block:: C++
|
|
|
|
CAF_BEGIN_TYPE_ID_BLOCK(my_project, caf::first_custom_type_id)
|
|
|
|
CAF_ADD_ATOM(my_project, add_atom)
|
|
CAF_ADD_ATOM(my_project, multiply_atom)
|
|
|
|
CAF_END_TYPE_ID_BLOCK(my_project)
|
|
|
|
behavior do_math{
|
|
[](add_atom, int32_t a, int32_t b) {
|
|
return a + b;
|
|
},
|
|
[](multiply_atom, int32_t a, int32_t b) {
|
|
return a * b;
|
|
}
|
|
};
|
|
|
|
|
|
// caller side: send(math_actor, add_atom_v, int32_t{1}, int32_t{2})
|
|
|
|
The macro ``CAF_ADD_ATOM`` defined an empty ``struct`` with the given name as
|
|
well as a ``constexpr`` variable for conveniently creating a value of that type
|
|
that uses the type name plus a ``_v`` suffix. In the example above,
|
|
``atom_value`` is the type name and ``atom_value_v`` is the constant.
|