Commit 30c319af authored by Dominik Charousset's avatar Dominik Charousset

Clean up manual sections and examples

parent f503a0f4
/******************************************************************************\
* This example program showcases how to manually manage socket IO using *
* a broker. Server and client exchange integers in a 'ping-pong protocol'. *
* *
* Minimal setup: *
* - ./build/bin/broker -s 4242 *
* - ./build/bin/broker -c localhost 4242 *
\
******************************************************************************/
// Manual refs: 42-47 (Actors.tex)
// This example program showcases how to manually manage socket I/O using a
// broker. Server and client exchange integers in a "ping-pong protocol".
//
// Minimal setup:
// - ./build/bin/broker -s 4242
// - ./build/bin/broker -c localhost 4242
#include "caf/config.hpp"
......@@ -39,12 +34,14 @@ using namespace caf::io;
namespace {
// --(rst-attach-begin)--
// Utility function to print an exit message with custom name.
void print_on_exit(const actor& hdl, const std::string& name) {
hdl->attach_functor([=](const error& reason) {
cout << name << " exited: " << to_string(reason) << endl;
});
}
// --(rst-attach-end)--
enum class op : uint8_t {
ping,
......
// Showcases how to add custom POD message types.
// Manual refs: 24-27, 30-34, 75-78, 81-84 (ConfiguringActorApplications)
// 23-33 (TypeInspection)
#include <cassert>
#include <iostream>
#include <string>
......@@ -117,4 +114,4 @@ void caf_main(actor_system& sys) {
self->send(t, foo_pair2{3, 4});
}
CAF_MAIN(id_block::custom_types_1)
CAF_MAIN(caf::id_block::custom_types_1)
// showcases how to add custom message types to CAF
// if friend access for serialization is available
// Showcases how to add custom message types to CAF if friend access for
// serialization is available.
#include <utility>
#include <iostream>
......
// Showcases custom message types that cannot provide
// friend access to the inspect() function.
// Showcases custom message types that cannot provide friend access to the
// inspect() function.
#include <iostream>
#include <utility>
......
/******************************************************************************\
* This example is an implementation of the classical Dining Philosophers *
* exercise using only libcaf's event-based actor implementation. *
\******************************************************************************/
// This example is an implementation of the classical Dining Philosophers
// exercise using only libcaf's event-based actor implementation.
#include <chrono>
#include <iostream>
......
/******************************************************************************\
* This example is a very basic, non-interactive math service implemented *
* for both the blocking and the event-based API. *
\******************************************************************************/
// Manual refs: lines 17-18, 21-26, 28-56, 58-92, 123-128 (Actor)
// This example is a very basic, non-interactive math service implemented for
// both the blocking and the event-based API.
#include <iostream>
......@@ -14,10 +10,13 @@ using namespace caf;
namespace {
// --(rst-actor-begin)--
using calculator_actor
= typed_actor<replies_to<add_atom, int32_t, int32_t>::with<int32_t>,
replies_to<sub_atom, int32_t, int32_t>::with<int32_t>>;
// --(rst-actor-end)--
// --(rst-fwd-begin)--
// prototypes and forward declarations
behavior calculator_fun(event_based_actor* self);
void blocking_calculator_fun(blocking_actor* self);
......@@ -25,7 +24,9 @@ calculator_actor::behavior_type typed_calculator_fun();
class calculator;
class blocking_calculator;
class typed_calculator;
// --(rst-fwd-end)--
// --(rst-funs-begin)--
// function-based, dynamically typed, event-based API
behavior calculator_fun(event_based_actor*) {
return {
......@@ -55,7 +56,9 @@ calculator_actor::behavior_type typed_calculator_fun() {
[](sub_atom, int32_t a, int32_t b) { return a - b; },
};
}
// --(rst-funs-end)--
// --(rst-classes-begin)--
// class-based, dynamically typed, event-based API
class calculator : public event_based_actor {
public:
......@@ -91,6 +94,7 @@ public:
return typed_calculator_fun();
}
};
// --(rst-classes-end)--
void tester(scoped_actor&) {
// end of recursion
......@@ -121,12 +125,14 @@ void tester(scoped_actor& self, const Handle& hdl, int32_t x, int32_t y,
}
void caf_main(actor_system& system) {
// --(rst-spawn-begin)--
auto a1 = system.spawn(blocking_calculator_fun);
auto a2 = system.spawn(calculator_fun);
auto a3 = system.spawn(typed_calculator_fun);
auto a4 = system.spawn<blocking_calculator>();
auto a5 = system.spawn<calculator>();
auto a6 = system.spawn<typed_calculator>();
// --(rst-spawn-end)--
scoped_actor self{system};
tester(self, a1, 1, 2, a2, 3, 4, a3, 5, 6, a4, 7, 8, a5, 9, 10, a6, 11, 12);
self->send_exit(a1, exit_reason::user_shutdown);
......
/******************************************************************************\
* This example is a very basic, non-interactive math service implemented *
* for both the blocking and the event-based API. *
\******************************************************************************/
// This file is partially included in the manual, do not modify
// without updating the references in the *.tex files!
// Manual references: lines 18-44, and 49-50 (Actor.tex)
// This example is a very basic, non-interactive math service implemented for
// both the blocking and the event-based API.
#include <cstdint>
#include <iostream>
......@@ -16,6 +10,7 @@ using std::cout;
using std::endl;
using namespace caf;
// --(rst-cell-begin)--
using cell = typed_actor<reacts_to<put_atom, int32_t>,
replies_to<get_atom>::with<int32_t>>;
......@@ -24,14 +19,19 @@ struct cell_state {
};
cell::behavior_type type_checked_cell(cell::stateful_pointer<cell_state> self) {
return {[=](put_atom, int32_t val) { self->state.value = val; },
[=](get_atom) { return self->state.value; }};
return {
[=](put_atom, int32_t val) { self->state.value = val; },
[=](get_atom) { return self->state.value; },
};
}
behavior unchecked_cell(stateful_actor<cell_state>* self) {
return {[=](put_atom, int32_t val) { self->state.value = val; },
[=](get_atom) { return self->state.value; }};
return {
[=](put_atom, int32_t val) { self->state.value = val; },
[=](get_atom) { return self->state.value; },
};
}
// --(rst-cell-end)--
void caf_main(actor_system& system) {
// create one cell for each implementation
......
/******************************************************************************\
* This example illustrates how to do time-triggered loops in libcaf. *
\******************************************************************************/
// This example illustrates how to do time-triggered loops in CAF.
#include <algorithm>
#include <chrono>
......@@ -8,29 +6,26 @@
#include "caf/all.hpp"
// This file is partially included in the manual, do not modify
// without updating the references in the *.tex files!
// Manual references: lines 58-75 (MessagePassing.tex)
using std::cout;
using std::endl;
using std::pair;
using namespace caf;
// ASCII art figures
// ASCII art figures.
constexpr const char* figures[] = {
"<(^.^<)",
"<(^.^)>",
"(>^.^)>",
};
// Bundes an index to an ASCII art figure plus its position on screen.
struct animation_step {
size_t figure_idx;
size_t offset;
};
// array of {figure, offset} pairs
// Array of {figure, offset} pairs.
constexpr animation_step animation_steps[] = {
{1, 7}, {0, 7}, {0, 6}, {0, 5}, {1, 5}, {2, 5}, {2, 6},
{2, 7}, {2, 8}, {2, 9}, {2, 10}, {1, 10}, {0, 10}, {0, 9},
......@@ -38,41 +33,48 @@ constexpr animation_step animation_steps[] = {
{0, 12}, {0, 11}, {0, 10}, {0, 9}, {0, 8}, {0, 7}, {1, 7},
};
// Width of the printed area.
constexpr size_t animation_width = 20;
// "draws" an animation step by printing "{offset_whitespaces}{figure}{padding}"
// Draws an animation step by printing "{offset_whitespaces}{figure}{padding}".
void draw_kirby(const animation_step& animation) {
cout.width(animation_width);
// override last figure
// Override last figure.
cout << '\r';
// print offset
// Print left padding (offset).
std::fill_n(std::ostream_iterator<char>{cout}, animation.offset, ' ');
// print figure
// Print figure.
cout << figures[animation.figure_idx];
// print padding
// Print right padding.
cout.fill(' ');
// make sure figure is printed
// Make sure figure is visible.
cout.flush();
}
// uses a message-based loop to iterate over all animation steps
void dancing_kirby(event_based_actor* self) {
// let's get it started
self->send(self, update_atom_v, size_t{0});
self->become([=](update_atom, size_t step) {
if (step == sizeof(animation_step)) {
// we've printed all animation steps (done)
cout << endl;
self->quit();
return;
}
// print given step
draw_kirby(animation_steps[step]);
// animate next step in 150ms
self->delayed_send(self, std::chrono::milliseconds(150), update_atom_v,
step + 1);
});
// --(rst-dancing-kirby-begin)--
// Uses a message-based loop to iterate over all animation steps.
behavior dancing_kirby(event_based_actor* self) {
// Let's get started.
auto i = std::begin(animation_steps);
auto e = std::end(animation_steps);
self->send(self, update_atom_v);
return {
[=](update_atom) mutable {
// We're done when reaching the past-the-end position.
if (i == e) {
cout << endl;
self->quit();
return;
}
// Print current animation step.
draw_kirby(*i);
// Animate next step in 150ms.
++i;
self->delayed_send(self, std::chrono::milliseconds(150), update_atom_v);
},
};
}
// --(rst-dancing-kirby-end)--
void caf_main(actor_system& system) {
system.spawn(dancing_kirby);
......
#include <cstdint>
#include <iostream>
#include "caf/all.hpp"
// This file is partially included in the manual, do not modify
// without updating the references in the *.tex files!
// Manual references: lines 15-36 (MessagePassing.tex)
using namespace caf;
// using add_atom = atom_constant<atom("add")>; (defined in atom.hpp)
// --(rst-delegate-begin)--
using calc = typed_actor<replies_to<add_atom, int32_t, int32_t>::with<int32_t>>;
void actor_a(event_based_actor* self, const calc& worker) {
......@@ -32,6 +28,7 @@ calc::behavior_type actor_c() {
[](add_atom, int32_t x, int32_t y) { return x + y; },
};
}
// --(rst-delegate-end)--
void caf_main(actor_system& system) {
system.spawn(actor_a, system.spawn(actor_b, system.spawn(actor_c)));
......
/******************************************************************************\
* A very basic, interactive divider. *
\******************************************************************************/
// Manual refs: 17-19, 49-59, 70-76 (MessagePassing);
// 17-47 (Error)
// A basic, interactive divider.
#include <iostream>
......@@ -14,10 +9,21 @@ using std::endl;
using std::flush;
using namespace caf;
enum class math_error : uint8_t;
CAF_BEGIN_TYPE_ID_BLOCK(divider, first_custom_type_id)
CAF_ADD_TYPE_ID(divider, (math_error))
CAF_END_TYPE_ID_BLOCK(divider)
// --(rst-divider-begin)--
enum class math_error : uint8_t {
division_by_zero = 1,
};
CAF_ERROR_CODE_ENUM(math_error)
std::string to_string(math_error x) {
switch (x) {
case math_error::division_by_zero:
......@@ -27,14 +33,6 @@ std::string to_string(math_error x) {
}
}
CAF_BEGIN_TYPE_ID_BLOCK(divider, first_custom_type_id)
CAF_ADD_TYPE_ID(divider, (math_error))
CAF_END_TYPE_ID_BLOCK(divider)
CAF_ERROR_CODE_ENUM(math_error)
using divider = typed_actor<replies_to<div_atom, double, double>::with<double>>;
divider::behavior_type divider_impl() {
......@@ -46,6 +44,7 @@ divider::behavior_type divider_impl() {
},
};
}
// --(rst-divider-end)--
void caf_main(actor_system& system) {
double x;
......@@ -54,6 +53,7 @@ void caf_main(actor_system& system) {
std::cin >> x;
cout << "y: " << flush;
std::cin >> y;
// --(rst-request-begin)--
auto div = system.spawn(divider_impl);
scoped_actor self{system};
self->request(div, std::chrono::seconds(10), div_atom_v, x, y)
......@@ -63,6 +63,7 @@ void caf_main(actor_system& system) {
aout(self) << "*** cannot compute " << x << " / " << y << " => "
<< to_string(err) << endl;
});
// --(rst-request-end)--
}
CAF_MAIN(id_block::divider)
#include "caf/all.hpp"
#include <cassert>
#include <cstdint>
#include <iostream>
#include "caf/all.hpp"
enum class ec : uint8_t {
push_to_full = 1,
pop_from_empty,
......
/******************************************************************************\
* Illustrates response promises. *
\******************************************************************************/
// This file is partially included in the manual, do not modify
// without updating the references in the *.tex files!
// Manual references: lines 18-43 (MessagePassing.tex)
// Illustrates how to use response promises.
#include <iostream>
......@@ -15,17 +9,16 @@ using std::endl;
using namespace caf;
// --(rst-promises-begin)--
using adder
= typed_actor<replies_to<add_atom, int32_t, int32_t>::with<int32_t>>;
// function-based, statically typed, event-based API
adder::behavior_type worker() {
return {
[](add_atom, int32_t a, int32_t b) { return a + b; },
};
}
// function-based, statically typed, event-based API
adder::behavior_type calculator_master(adder::pointer self) {
auto w = self->spawn(worker);
return {
......@@ -38,6 +31,7 @@ adder::behavior_type calculator_master(adder::pointer self) {
},
};
}
// --(rst-promises-end)--
void caf_main(actor_system& system) {
auto f = make_function_view(system.spawn(calculator_master));
......
/******************************************************************************\
* Illustrates semantics of request().{then|await|receive}. *
\******************************************************************************/
// This file is partially included in the manual, do not modify
// without updating the references in the *.tex files!
// Manual references: lines 20-37, 39-51, 53-64, 67-69 (MessagePassing.tex)
// Illustrates semantics of request().{then|await|receive}.
#include <chrono>
#include <cstdint>
......@@ -16,8 +10,10 @@
using std::endl;
using std::vector;
using std::chrono::seconds;
using namespace caf;
// --(rst-cell-begin)--
using cell = typed_actor<reacts_to<put_atom, int32_t>,
replies_to<get_atom>::with<int32_t>>;
......@@ -59,11 +55,14 @@ void blocking_testee(blocking_actor* self, vector<cell> cells) {
aout(self) << "cell #" << x.id() << " -> " << to_string(err) << endl;
});
}
// --(rst-cell-end)--
void caf_main(actor_system& system) {
vector<cell> cells;
// --(rst-spawn-begin)--
for (auto i = 0; i < 5; ++i)
cells.emplace_back(system.spawn(cell_impl, i * i));
// --(rst-spawn-end)--
scoped_actor self{system};
aout(self) << "waiting_testee" << endl;
auto x1 = self->spawn(waiting_testee, cells);
......
/******************************************************************************\
* This example is a very basic, non-interactive math service implemented *
* using typed actors. *
\******************************************************************************/
// This example is a very basic, non-interactive math service implemented using
// typed actors.
#include "caf/all.hpp"
#include <cassert>
......
......@@ -9,8 +9,6 @@
// Run client at the same host:
// - ./build/bin/distributed_math_actor -c -p 4242
// Manual refs: 206-218 (ConfiguringActorSystems)
#include <array>
#include <cassert>
#include <functional>
......@@ -199,6 +197,7 @@ optional<int> toint(const string& str) {
return none;
}
// --(rst-config-begin)--
class config : public actor_system_config {
public:
uint16_t port = 0;
......@@ -212,6 +211,7 @@ public:
.add(server_mode, "server-mode,s", "enable server mode");
}
};
// --(rst-config-end)--
void client_repl(actor_system& system, const config& cfg) {
// keeps track of requests and tries to reconnect on server failures
......
/******************************************************************************\
* This example program represents a minimal terminal chat program *
* based on group communication. *
* *
* Setup for a minimal chat between "alice" and "bob": *
* - ./build/bin/group_chat -s -p 4242 *
* - ./build/bin/group_chat -g remote:chatroom@localhost:4242 -n alice *
* - ./build/bin/group_chat -g remote:chatroom@localhost:4242 -n bob *
\******************************************************************************/
// This example program represents a minimal terminal chat program based on
// group communication.
//
// Setup for a minimal chat between "alice" and "bob":
// - ./build/bin/group_chat -s -p 4242
// - ./build/bin/group_chat -g remote:chatroom@localhost:4242 -n alice
// - ./build/bin/group_chat -g remote:chatroom@localhost:4242 -n bob
#include <cstdlib>
#include <iostream>
......
/******************************************************************************\
* This example program represents a minimal IRC-like group *
* communication server. *
* *
* Setup for a minimal chat between "alice" and "bob": *
* - ./build/bin/group_server -p 4242 *
* - ./build/bin/group_chat -g remote:chatroom@localhost:4242 -n alice *
* - ./build/bin/group_chat -g remote:chatroom@localhost:4242 -n bob *
\ ******************************************************************************/
// This example program represents a minimal IRC-like group communication
// server.
//
// Setup for a minimal chat between "alice" and "bob":
// - ./build/bin/group_server -p 4242
// - ./build/bin/group_chat -g remote:chatroom@localhost:4242 -n alice
// - ./build/bin/group_chat -g remote:chatroom@localhost:4242 -n bob
#include <string>
#include <cstdlib>
......
// This program illustrates how to spawn a simple calculator
// across the network.
// This program illustrates how to spawn a simple calculator across the network.
//
// Run server at port 4242:
// - remote_spawn -s -p 4242
......
/******************************************************************************
* Basic, non-interactive streaming example for processing integers. *
******************************************************************************/
// Basic, non-interactive streaming example for processing integers.
#include <iostream>
#include <vector>
......
// Manual refs: lines 12-60 (Testing)
#define CAF_SUITE ping_pong
#include "caf/test/dsl.hpp"
......@@ -9,6 +7,7 @@
using namespace caf;
// --(rst-ping-pong-begin)--
namespace {
behavior ping(event_based_actor* self, actor pong_actor, int n) {
......@@ -58,3 +57,4 @@ CAF_TEST(three pings) {
}
CAF_TEST_FIXTURE_SCOPE_END()
// --(rst-ping-pong-end)--
......@@ -16,9 +16,6 @@
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
// This file is referenced in the manual, do not modify without updating refs!
// ConfiguringActorApplications: 50-54
#pragma once
#include <type_traits>
......@@ -45,9 +42,10 @@ struct is_allowed_unsafe_message_type<const T&>
} // namespace caf
// --(rst-macro-begin)--
#define CAF_ALLOW_UNSAFE_MESSAGE_TYPE(type_name) \
namespace caf { \
template <> \
struct allowed_unsafe_message_type<type_name> : std::true_type {}; \
}
// --(rst-macro-end)--
......@@ -16,10 +16,6 @@
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
// This file is partially included in the manual, do not modify
// without updating the references in the *.tex files!
// Manual references: lines 29-49 (Error.tex)
#pragma once
#include <string>
......@@ -28,6 +24,7 @@
namespace caf {
// --(rst-exit-reason-begin)--
/// This error category represents fail conditions for actors.
enum class exit_reason : uint8_t {
/// Indicates that an actor finished execution without error.
......@@ -49,6 +46,7 @@ enum class exit_reason : uint8_t {
/// Indicates that an actor was killed because it became unreachable.
unreachable
};
// --(rst-exit-reason-end)--
/// Returns a string representation of given exit reason.
std::string to_string(exit_reason);
......
......@@ -16,10 +16,6 @@
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
// This file is partially included in the manual, do not modify
// without updating the references in the *.tex files!
// Manual references: lines 32-117 (Error.tex)
#pragma once
#include "caf/error.hpp"
......@@ -27,6 +23,7 @@
namespace caf {
// --(rst-sec-begin)--
/// SEC stands for "System Error Code". This enum contains error codes for
/// ::actor_system and its modules.
enum class sec : uint8_t {
......@@ -140,6 +137,7 @@ enum class sec : uint8_t {
/// Disconnected from a BASP node after reaching the connection timeout.
connection_timeout,
};
// --(rst-sec-end)--
/// @relates sec
std::string to_string(sec);
......
This diff is collapsed.
This diff is collapsed.
......@@ -12,55 +12,63 @@ include a message to provide additional context information.
Class Interface
---------------
+-----------------------------------------+--------------------------------------------------------------------+
| **Constructors** | |
+-----------------------------------------+--------------------------------------------------------------------+
| ``(Enum x)`` | Construct error by calling ``make_error(x)`` |
+-----------------------------------------+--------------------------------------------------------------------+
| ``(uint8_t x, atom_value y)`` | Construct error with code ``x`` and category ``y`` |
+-----------------------------------------+--------------------------------------------------------------------+
| ``(uint8_t x, atom_value y, message z)``| Construct error with code ``x``, category ``y``, and context ``z`` |
+-----------------------------------------+--------------------------------------------------------------------+
| | |
+-----------------------------------------+--------------------------------------------------------------------+
| **Observers** | |
+-----------------------------------------+--------------------------------------------------------------------+
| ``uint8_t code()`` | Returns the error code |
+-----------------------------------------+--------------------------------------------------------------------+
| ``atom_value category()`` | Returns the error category |
+-----------------------------------------+--------------------------------------------------------------------+
| ``message context()`` | Returns additional context information |
+-----------------------------------------+--------------------------------------------------------------------+
| ``explicit operator bool()`` | Returns ``code() != 0`` |
+-----------------------------------------+--------------------------------------------------------------------+
+------------------------------------------+--------------------------------------------------------------------+
| **Constructors** |
+------------------------------------------+--------------------------------------------------------------------+
| ``(Enum x)`` | Construct error by calling ``make_error(x)`` |
+------------------------------------------+--------------------------------------------------------------------+
| ``(uint8_t x, atom_value y)`` | Construct error with code ``x`` and category ``y`` |
+------------------------------------------+--------------------------------------------------------------------+
| ``(uint8_t x, atom_value y, message z)`` | Construct error with code ``x``, category ``y``, and context ``z`` |
+------------------------------------------+--------------------------------------------------------------------+
| **Observers** |
+------------------------------------------+--------------------------------------------------------------------+
| ``uint8_t code()`` | Returns the error code |
+------------------------------------------+--------------------------------------------------------------------+
| ``atom_value category()`` | Returns the error category |
+------------------------------------------+--------------------------------------------------------------------+
| ``message context()`` | Returns additional context information |
+------------------------------------------+--------------------------------------------------------------------+
| ``explicit operator bool()`` | Returns ``code() != 0`` |
+------------------------------------------+--------------------------------------------------------------------+
.. _custom-error:
Add Custom Error Categories
---------------------------
Adding custom error categories requires three steps: (1) declare an enum class
of type ``uint8_t`` with the first value starting at 1, (2) specialize
``error_category`` to give your type a custom ID (value 0-99 are
reserved by CAF), and (3) add your error category to the actor system config.
The following example adds custom error codes for math errors.
Adding custom error categories requires two steps: (1) declare an enum class of
type ``uint8_t`` with the first value starting at 1, (2) enable conversions from
the error code enum to ``error`` by using the macro ``CAF_ERROR_CODE_ENUM``. The
following example illustrates these two steps with a ``math_error`` enum to
signal divisions by zero:
.. literalinclude:: /examples/message_passing/divider.cpp
:language: C++
:lines: 17-47
:start-after: --(rst-divider-begin)--
:end-before: --(rst-divider-end)--
.. note::
CAF stores the integer value with an atom that disambiguates error codes from
different categories. The macro ``CAF_ERROR_CODE_ENUM`` uses the given type
name as atom as well. This means the same limitations apply. In particular,
10 characters or less. If the type name exceeds this limit, users can pass
two arguments instead. In the example above, we could call
``CAF_ERROR_CODE_ENUM(math_error, "math")`` instead.
.. _sec:
System Error Codes
------------------
System Error Codes (SECs) use the error category ``"system"``. They
represent errors in the actor system or one of its modules and are defined as
follows.
System Error Codes (SECs) represent errors in the actor system or one of its
modules and are defined as follows:
.. literalinclude:: /libcaf_core/caf/sec.hpp
:language: C++
:lines: 32-117
:start-after: --(rst-sec-begin)--
:end-before: --(rst-sec-end)--
.. _exit-reason:
......@@ -77,5 +85,5 @@ in the same way. The latter terminates an actor unconditionally when used in
.. literalinclude:: /libcaf_core/caf/exit_reason.hpp
:language: C++
:lines: 29-49
:start-after: --(rst-exit-reason-begin)--
:end-before: --(rst-exit-reason-end)--
......@@ -14,8 +14,8 @@ name, joining, and leaving.
std::string id = "foo";
auto expected_grp = system.groups().get(module, id);
if (! expected_grp) {
std::cerr << "*** cannot load group: "
<< system.render(expected_grp.error()) << std::endl;
std::cerr << "*** cannot load group: " << to_string(expected_grp.error())
<< std::endl;
return;
}
auto grp = std::move(*expected_grp);
......
......@@ -45,10 +45,10 @@ Dynamically Typed Actor
~~~~~~~~~~~~~~~~~~~~~~~
A dynamically typed actor accepts any kind of message and dispatches on its
content dynamically at the receiver. This is the "traditional" messaging
style found in implementations like Erlang or Akka. The upside of this approach
is (usually) faster prototyping and less code. This comes at the cost of
requiring excessive testing.
content dynamically at the receiver. This is the traditional messaging style
found in implementations like Erlang or Akka. The upside of this approach is
(usually) faster prototyping and less code. This comes at the cost of requiring
excessive testing.
Statically Typed Actor
~~~~~~~~~~~~~~~~~~~~~~
......
......@@ -85,10 +85,9 @@ This policy models split/join or scatter/gather work flows, where a work item
is split into as many tasks as workers are available and then the individuals
results are joined together before sending the full result back to the client.
The join function is responsible for ``glueing'' all result messages together
to create a single result. The function is called with the result object
(initialed using ``init``) and the current result messages from a
worker.
The join function is responsible for ``glueing`` all result messages together to
create a single result. The function is called with the result object (initialed
using ``init``) and the current result messages from a worker.
The first argument of a split function is a mapping from actors (workers) to
tasks (messages). The second argument is the input message. The default split
......
......@@ -19,7 +19,7 @@ change its behavior when not receiving message after a certain amount of time.
message_handler x1{
[](int i) { /*...*/ },
[](double db) { /*...*/ },
[](int a, int b, int c) { /*...*/ }
[](int a, int b, int c) { /*...*/ },
};
In our first example, ``x1`` models a behavior accepting messages that consist
......@@ -31,7 +31,7 @@ other message is not matched and gets forwarded to the default handler (see
message_handler x2{
[](double db) { /*...*/ },
[](double db) { /* - unreachable - */ }
[](double db) { /* - unreachable - */ },
};
Our second example illustrates an important characteristic of the matching
......@@ -55,37 +55,54 @@ The handler ``x4`` will consume messages with a single
``double`` using the first callback in ``x2``, essentially
overriding the second callback in ``x1``.
.. code-block:: C++
behavior bhvr{
[](int i) { /*...*/ },
[](double db) { /*...*/ },
[](int a, int b, int c) { /*...*/ },
after(std::chrono::seconds(10)) >> [] { /*..*/ },
};
Similar to ``message_handler``, ``behavior`` stores a set of functions.
Additionally, behaviors accept optional timeouts as *last* argument to the
constructor. In the example above, the behavior calls the timeout handler after
receiving no messages for 10 seconds. This timeout triggers repeatedly, i.e.,
the timeout handler gets called again after 20 seconds when not receiving any
messages, then again after 30 seconds, and so on.
.. _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
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.
actor cannot decide whether it should multiply or add the integers, for example.
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 mapped to integer values at compile time. This mapping is
guaranteed to be collision-free and invertible, but limits atom literals to ten
characters and prohibits special characters. Legal characters are
``_0-9A-Za-z`` and the whitespace character. Atoms are created using
the ``constexpr`` function ``atom``, as the following example
illustrates.
characters and prohibits special characters. Legal characters are ``_0-9A-Za-z``
and the whitespace character. Atoms are created using the ``constexpr`` function
``atom``, as the following example illustrates.
.. code-block:: C++
atom_value a1 = atom("add");
atom_value a2 = atom("multiply");
**Warning**: The compiler cannot enforce the restrictions at compile time,
except for a length check. The assertion ``atom("!?") != atom("?!")``
is not true, because each invalid character translates to a whitespace
character.
.. warning::
The compiler cannot enforce the restrictions at compile time, except for a
length check. The assertion ``atom("!?") != atom("?!")`` is not true, because
each invalid character translates to a whitespace character.
While the ``atom_value`` is computed at compile time, it is not
uniquely typed and thus cannot be used in the signature of a callback. To
......
This diff is collapsed.
......@@ -92,12 +92,10 @@ connect to the published actor by calling ``remote_actor``:
// node B
auto ping = system.middleman().remote_actor("node A", 4242);
if (! ping) {
cerr << "unable to connect to node A: "
<< system.render(ping.error()) << std::endl;
} else {
if (!ping)
cerr << "unable to connect to node A: " << to_string(ping.error()) << '\n';
else
self->send(*ping, ping_atom::value);
}
There is no difference between server and client after the connection phase.
Remote actors use the same handle types as local actors and are thus fully
......
......@@ -4,23 +4,32 @@ Overview
Compiling CAF requires CMake and a C++11-compatible compiler. To get and
compile the sources on UNIX-like systems, type the following in a terminal:
.. ::
.. code-block:: none
git clone https://github.com/actor-framework/actor-framework
cd actor-framework
./configure
make
make install [as root, optional]
make -C build
make -C build test [optional]
make -C build install [as root, optional]
We recommended to run the unit tests as well:
If the output of ``make test`` indicates an error, please submit a bug report
that includes (a) your compiler version, (b) your OS, and (c) the content of the
file ``build/Testing/Temporary/LastTest.log``.
.. ::
The configure script provides several build options for advanced configuration.
Please run ``./configure -h`` to see all available options. If you are building
CAF only as a dependency, disabling the unit tests and the examples can safe you
some time during the build.
make test
.. note::
If the output indicates an error, please submit a bug report that includes (a)
your compiler version, (b) your OS, and (c) the content of the file
``build/Testing/Temporary/LastTest.log``.
The ``configure`` script provides a convenient way for creating a build
directory and calling CMake. Users that are familiar with CMake can of course
also use CMake directly and avoid using the ``configure`` script entirely.
On Windows, we recomment using the CMake GUI to generate a Visual Studio
project file for CAF.
Features
--------
......
......@@ -19,32 +19,28 @@ It is worth mentioning that the registry is not synchronized between connected
actor system. Each actor system has its own, local registry in a distributed
setting.
+-------------------------------------------+-------------------------------------------------+
| **Types** | |
+-------------------------------------------+-------------------------------------------------+
| ``name_map`` | ``unordered_map<atom_value, strong_actor_ptr>`` |
+-------------------------------------------+-------------------------------------------------+
| | |
+-------------------------------------------+-------------------------------------------------+
| **Observers** | |
+-------------------------------------------+-------------------------------------------------+
| ``strong_actor_ptr get(actor_id)`` | Returns the actor associated to given ID. |
+-------------------------------------------+-------------------------------------------------+
| ``strong_actor_ptr get(atom_value)`` | Returns the actor associated to given name. |
+-------------------------------------------+-------------------------------------------------+
| ``name_map named_actors()`` | Returns all name mappings. |
+-------------------------------------------+-------------------------------------------------+
| ``size_t running()`` | Returns the number of currently running actors. |
+-------------------------------------------+-------------------------------------------------+
| | |
+-------------------------------------------+-------------------------------------------------+
| **Modifiers** | |
+-------------------------------------------+-------------------------------------------------+
| ``void put(actor_id, strong_actor_ptr)`` | Maps an actor to its ID. |
+-------------------------------------------+-------------------------------------------------+
| ``void erase(actor_id)`` | Removes an ID mapping from the registry. |
+-------------------------------------------+-------------------------------------------------+
| ``void put(atom_value, strong_actor_ptr)``| Maps an actor to a name. |
+-------------------------------------------+-------------------------------------------------+
| ``void erase(atom_value)`` | Removes a name mapping from the registry. |
+-------------------------------------------+-------------------------------------------------+
+---------------------------------------+-------------------------------------------------+
| **Types** |
+---------------------------------------+-------------------------------------------------+
| ``name_map`` | ``unordered_map<atom_value, strong_actor_ptr>`` |
+---------------------------------------+-------------------------------------------------+
| **Observers** |
+---------------------------------------+-------------------------------------------------+
| ``strong_actor_ptr get(actor_id)`` | Returns the actor associated to given ID. |
+---------------------------------------+-------------------------------------------------+
| ``strong_actor_ptr get(atom_value)`` | Returns the actor associated to given name. |
+---------------------------------------+-------------------------------------------------+
| ``name_map named_actors()`` | Returns all name mappings. |
+---------------------------------------+-------------------------------------------------+
| ``size_t running()`` | Returns the number of currently running actors. |
+---------------------------------------+-------------------------------------------------+
| **Modifiers** |
+---------------------------------------+-------------------------------------------------+
| ``put(actor_id, strong_actor_ptr)`` | Maps an actor to its ID. |
+---------------------------------------+-------------------------------------------------+
| ``erase(actor_id)`` | Removes an ID mapping from the registry. |
+---------------------------------------+-------------------------------------------------+
| ``put(atom_value, strong_actor_ptr)`` | Maps an actor to a name. |
+---------------------------------------+-------------------------------------------------+
| ``erase(atom_value)`` | Removes a name mapping from the registry. |
+---------------------------------------+-------------------------------------------------+
......@@ -10,7 +10,8 @@ named "calculator".
.. literalinclude:: /examples/remoting/remote_spawn.cpp
:language: C++
:lines: 123-137
:start-after: --(rst-client-begin)--
:end-before: --(rst-client-end)--
We first connect to a CAF node with ``middleman().connect(...)``. On success,
``connect`` returns the node ID we need for ``remote_spawn``. This requires the
......
......@@ -69,48 +69,50 @@ Defining Sources
.. literalinclude:: /examples/streaming/integer_stream.cpp
:language: C++
:lines: 17-48
:start-after: --(rst-source-begin)--
:end-before: --(rst-source-end)--
The simplest way to defining a source is to use the
``attach_stream_source`` function and pass it four arguments: a pointer
to *self*, *initializer* for the state, *generator* for
producing values, and *predicate* for signaling the end of the stream.
The simplest way to defining a source is to use the ``attach_stream_source``
function and pass it four arguments: a pointer to *self*, *initializer* for the
state, *generator* for producing values, and *predicate* for signaling the end
of the stream.
Defining Stages
---------------
.. literalinclude:: /examples/streaming/integer_stream.cpp
:language: C++
:lines: 50-83
:start-after: --(rst-stage-begin)--
:end-before: --(rst-stage-end)--
The function ``make_stage`` also takes three lambdas but additionally
the received input stream handshake as first argument. Instead of a predicate,
``make_stage`` only takes a finalizer, since the stage does not produce
data on its own and a stream terminates if no more sources exist.
The function ``make_stage`` also takes three lambdas but additionally the
received input stream handshake as first argument. Instead of a predicate,
``make_stage`` only takes a finalizer, since the stage does not produce data on
its own and a stream terminates if no more sources exist.
Defining Sinks
--------------
.. literalinclude:: /examples/streaming/integer_stream.cpp
:language: C++
:lines: 85-114
:start-after: --(rst-sink-begin)--
:end-before: --(rst-sink-end)--
The function ``make_sink`` is similar to ``make_stage``, except
that is does not produce outputs.
The function ``make_sink`` is similar to ``make_stage``, except that is does not
produce outputs.
Initiating Streams
------------------
.. literalinclude:: /examples/streaming/integer_stream.cpp
:language: C++
:lines: 128-132
:start-after: --(rst-main-begin)--
:end-before: --(rst-main-end)--
In our example, we always have a source ``int_source`` and a sink
``int_sink`` with an optional stage ``int_selector``. Sending
``open_atom`` to the source initiates the stream and the source will
respond with a stream handshake.
In our example, we always have a source ``int_source`` and a sink ``int_sink``
with an optional stage ``int_selector``. Sending ``open_atom`` to the source
initiates the stream and the source will respond with a stream handshake.
Using the actor composition in CAF (``snk * src`` reads *sink
after source*) allows us to redirect the stream handshake we send in
``caf_main`` to the sink (or to the stage and then from the stage to
the sink).
Using the actor composition in CAF (``snk * src`` reads *sink after source*)
allows us to redirect the stream handshake we send in ``caf_main`` to the sink
(or to the stage and then from the stage to the sink).
......@@ -99,7 +99,7 @@ as ``CAF_CHECK`` and provide tests rather than implementing a
},
[&](caf::error& err) {
// Must not happen, stop test.
CAF_FAIL(sys.render(err));
CAF_FAIL(err);
});
}
......@@ -127,18 +127,16 @@ configuration classes.
Using this fixture unlocks three additional macros:
* ``expect`` checks for a single message. The macro verifies the content types of the message and invokes the necessary member functions on the test coordinator. Optionally, the macro checks the receiver of the message and its content. If the expected message does not exist, the test aborts.
* ``allow`` is similar to ``expect``, but it does not abort the test if the expected message is missing. This macro returns ``true`` if the allowed message was delivered, ``false`` otherwise.
* ``disallow`` aborts the test if a particular message was delivered to an actor.
- ``expect`` checks for a single message. The macro verifies the content types of the message and invokes the necessary member functions on the test coordinator. Optionally, the macro checks the receiver of the message and its content. If the expected message does not exist, the test aborts.
- ``allow`` is similar to ``expect``, but it does not abort the test if the expected message is missing. This macro returns ``true`` if the allowed message was delivered, ``false`` otherwise.
- ``disallow`` aborts the test if a particular message was delivered to an actor.
The following example implements two actors, ``ping`` and
``pong``, that exchange a configurable amount of messages. The test
*three pings* then checks the contents of each message with
``expect`` and verifies that no additional messages exist using
``disallow``.
The following example implements two actors, ``ping`` and ``pong``, that
exchange a configurable amount of messages. The test *three pings* then checks
the contents of each message with ``expect`` and verifies that no additional
messages exist using ``disallow``.
.. literalinclude:: /examples/testing/ping_pong.cpp
:language: C++
:lines: 12-60
:start-after: --(rst-ping-pong-begin)--
:end-before: --(rst-ping-pong-end)--
.. _type-inspection:
Type Inspection (Serialization and String Conversion)
=====================================================
Type Inspection
===============
CAF is designed with distributed systems in mind. Hence, all message types must
be serializable and need a platform-neutral, unique name that is configured at
startup (see :ref:`add-custom-message-type`). Using a message type that is not
serializable causes a compiler error (see :ref:`unsafe-message-type`). CAF
serializes individual elements of a message by using the inspection API. This
API allows users to provide code for serialization as well as string conversion
be serializable and need a platform-neutral, unique name (see
:ref:`add-custom-message-type`). Using a message type that is not serializable
causes a compiler error (see :ref:`unsafe-message-type`). CAF serializes
individual elements of a message by using the inspection API. This API allows
users to provide code for serialization as well as string conversion, usually
with a single free function. The signature for a class ``my_class`` is always as
follows:
......@@ -21,38 +21,41 @@ follows:
The function ``inspect`` passes meta information and data fields to the
variadic call operator of the inspector. The following example illustrates an
implementation for ``inspect`` for a simple POD struct.
implementation of ``inspect`` for a simple POD called ``foo``.
.. literalinclude:: /examples/custom_type/custom_types_1.cpp
:language: C++
:lines: 23-33
:start-after: --(rst-foo-begin)--
:end-before: --(rst-foo-end)--
The inspector recursively inspects all data fields and has builtin support for
(1) ``std::tuple``, (2) ``std::pair``, (3) C arrays, (4) any
container type with ``x.size()``, ``x.empty()``,
``x.begin()`` and ``x.end()``.
``std::tuple``, ``std::pair``, arrays, as well as containers that provide the
member functions ``size``, ``empty``, ``begin`` and ``end``.
We consciously made the inspect API as generic as possible to allow for
extensibility. This allows users to use CAF's types in other contexts, to
implement parsers, etc.
.. note::
When converting a user-defined type to a string, CAF calls user-defined
``to_string`` functions and prefers those over ``inspect``.
Inspector Concept
-----------------
The following concept class shows the requirements for inspectors. The
placeholder ``T`` represents any user-defined type. For example,
``error`` when performing I/O operations or some integer type when
implementing a hash function.
placeholder ``T`` represents any user-defined type. Usually ``error`` or
``error_code``.
.. code-block:: C++
Inspector {
using result_type = T;
if (inspector only requires read access to the state of T)
static constexpr bool reads_state = true;
else
static constexpr bool writes_state = true;
static constexpr bool reads_state = ...;
static constexpr bool writes_state = ...;
template <class... Ts>
result_type operator()(Ts&&...);
......@@ -63,6 +66,13 @@ references. A loading ``Inspector`` must only accept mutable lvalue
references to data fields, but still allow for constant lvalue references and
rvalue references to annotations.
Inspectors that only visit data fields (such as a serializer) sets
``reads_state`` to ``true`` and ``writes_state`` to ``false``. Inspectors that
override data fields (such as a deserializer) assign the inverse values.
These compile-time constants can be used in ``if constexpr`` statements or to
select different ``inspect`` overloads with ``enable_if``.
Annotations
-----------
......@@ -74,27 +84,28 @@ inspector can query whether a type ``T`` is an annotation with
call operator of the inspector along with data fields. The following list shows
all annotations supported by CAF:
* ``type_name(n)``: Display type name as ``n`` in human-friendly output (position before data fields).
* ``hex_formatted()``: Format the following data field in hex format.
* ``omittable()``: Omit the following data field in human-friendly output.
* ``omittable_if_empty()``: Omit the following data field if it is empty in human-friendly output.
* ``omittable_if_none()``: Omit the following data field if it equals ``none`` in human-friendly output.
* ``save_callback(f)``: Call ``f`` when serializing (position after data fields).
* ``load_callback(f)``: Call ``f`` after deserializing all data fields (position after data fields).
- ``type_name(n)``: Display type name as ``n`` in human-friendly output
(position before data fields).
- ``hex_formatted()``: Format the following data field in hex format.
- ``omittable()``: Omit the following data field in human-friendly output.
- ``omittable_if_empty()``: Omit the following data field in human-friendly
output if it is empty.
- ``omittable_if_none()``: Omit the following data field in human-friendly
output if it equals ``none``.
- ``save_callback(f)``: Call ``f`` if ``reads_state == true``. Pass this
callback after the data fields.
- ``load_callback(f)``: Call ``f`` ``writes_state == true``. Pass this callback
after the data fields.
Backwards and Third-party Compatibility
---------------------------------------
CAF evaluates common free function other than ``inspect`` in order to
simplify users to integrate CAF into existing code bases.
CAF evaluates common free function other than ``inspect`` in order to simplify
users to integrate CAF into existing code bases.
Serializers and deserializers call user-defined ``serialize``
functions. Both types support ``operator&`` as well as
``operator()`` for individual data fields. A ``serialize``
function has priority over ``inspect``.
When converting a user-defined type to a string, CAF calls user-defined
``to_string`` functions and prefers those over ``inspect``.
Serializers and deserializers call user-defined ``serialize`` functions. Both
types support ``operator&`` as well as ``operator()`` for individual data
fields. A ``serialize`` function has priority over ``inspect``.
.. _unsafe-message-type:
......@@ -102,32 +113,57 @@ Whitelisting Unsafe Message Types
---------------------------------
Message types that are not serializable cause compile time errors when used in
actor communication. When using CAF for concurrency only, this errors can be
suppressed by whitelisting types with
``CAF_ALLOW_UNSAFE_MESSAGE_TYPE``. The macro is defined as follows.
actor communication. For messages that never cross the network, this errors can
be suppressed by whitelisting types with ``CAF_ALLOW_UNSAFE_MESSAGE_TYPE``. The
macro is defined as follows.
.. literalinclude:: /libcaf_core/caf/allowed_unsafe_message_type.hpp
:language: C++
:start-after: --(rst-macro-begin)--
:end-before: --(rst-macro-end)--
Keep in mind that ``unsafe`` means that your program runs into undefined
behavior (or segfaults) when you break your promise and try to serialize
messages that contain unsafe message types.
Splitting Save and Load Operations
----------------------------------
Saving and Loading with Getters and Setters
-------------------------------------------
If loading and storing cannot be implemented in a single function, users can
query whether the inspector is loading or storing. For example, consider the
following class ``foo`` with getter and setter functions and no public
access to its members.
Many classes shield their member variables with getter and setter functions.
This can be addressed by declaring the ``inspect`` function as ``friend``, but
only when not dealing with 3rd party library types. For example, consider the
following class ``foo`` with getter and setter functions and no public access to
its members.
.. literalinclude:: /examples/custom_type/custom_types_3.cpp
:language: C++
:lines: 20-49
:start-after: --(rst-foo-begin)--
:end-before: --(rst-foo-end)--
Since there is no access to the data fields ``a_`` and ``b_``
(and assuming no changes to ``foo`` are possible), we need to split our
implementation of ``inspect`` as shown below.
Since there is no access to the data fields ``a_`` and ``b_`` (and assuming no
changes to ``foo`` are possible), we can serialize or deserialize from/to local
variables and use a load callback to write back to the object when loading
state.
.. literalinclude:: /examples/custom_type/custom_types_3.cpp
:language: C++
:lines: 76-103
:start-after: --(rst-inspect-begin)--
:end-before: --(rst-inspect-end)--
The purpose of the scope guard in the example above is to write the content of
the temporaries back to ``foo`` at scope exit automatically. Storing
the result of ``f(...)`` in a temporary first and then writing the
changes to ``foo`` is not possible, because ``f(...)`` can
return ``void``.
For more complicated cases, we can also split the inspect overload as follows:
.. code-block:: C++
template <class Inspector>
typename std::enable_if<Inspector::reads_state,
typename Inspector::result_type>::type
inspect(Inspector& f, my_class& x) {
// ... serializing ...
}
template <class Inspector>
typename std::enable_if<Inspector::writes_state,
typename Inspector::result_type>::type
inspect(Inspector& f, my_class& x) {
// ... deserializing ...
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment