Commit 4406ed26 authored by Dominik Charousset's avatar Dominik Charousset

Remove composable_behavior

The composable behavior was intended to make actor states re-usable.
However, they turned out to not deliver on that promise.

One major flaw in this approach comes from the way inheritance works in
C++. In a scenario with multiple base types providing the same virtual
function, overrides in one base type may not override all functions for
that signature. Consider the following example:

```c++
struct foo {
    virtual void hello() = 0;
};

struct bar {
    virtual void hello() = 0;
};

struct a : foo {
  void hello() override {
      // nop
  }
};

struct b : bar, a {

};

int main(int, char **) {
    b obj; // ... oops: compiler error!
}
```

This rarely becomes an issue in most C++ code. However, it's very easy
to run into this compiler error using the composable behaviors by
implementing overlapping typed actor handles.

Aside from this subtle issue, compiler errors produced by the composable
actor API turned out to be very hard to parse. Getting a handler
signature wrong or missing an override quickly turns into guess games.

Last but not least, the `caf::param` API became a necessary evil to
support actors that may or may not require mutable access to a tuple.
But it adds another layer of complexity as well as runtime overhead.

The idea of implementing actors with re-usable pieces is still worth
pursuing, but this experiment did not pay out.
parent 5cd9cb5e
...@@ -35,10 +35,6 @@ add(streaming integer_stream) ...@@ -35,10 +35,6 @@ add(streaming integer_stream)
add(dynamic_behavior skip_messages) add(dynamic_behavior skip_messages)
add(dynamic_behavior dining_philosophers) add(dynamic_behavior dining_philosophers)
# composing actors using states or other actors
add(composition calculator_behavior)
add(composition dictionary_behavior)
# adding custom message types # adding custom message types
add(custom_type custom_types_1) add(custom_type custom_types_1)
add(custom_type custom_types_2) add(custom_type custom_types_2)
......
/******************************************************************************\
* This example is a very basic, non-interactive math service implemented *
* using composable states. *
\******************************************************************************/
// This file is partially included in the manual, do not modify
// without updating the references in the *.tex files!
// Manual references: lines 20-52 (Actor.tex)
#include <iostream>
#include "caf/all.hpp"
using std::cout;
using std::endl;
using namespace caf;
namespace {
using adder = typed_actor<replies_to<add_atom, int, int>::with<int>>;
using multiplier = typed_actor<replies_to<mul_atom, int, int>::with<int>>;
class adder_bhvr : public composable_behavior<adder> {
public:
result<int> operator()(add_atom, int x, int y) override {
return x + y;
}
};
class multiplier_bhvr : public composable_behavior<multiplier> {
public:
result<int> operator()(mul_atom, int x, int y) override {
return x * y;
}
};
// calculator_bhvr can be inherited from or composed further
using calculator_bhvr = composed_behavior<adder_bhvr, multiplier_bhvr>;
void caf_main(actor_system& system) {
auto f = make_function_view(system.spawn<calculator_bhvr>());
cout << "10 + 20 = " << f(add_atom_v, 10, 20) << endl;
cout << "7 * 9 = " << f(mul_atom_v, 7, 9) << endl;
}
} // namespace
CAF_MAIN()
/******************************************************************************\
* This example is a simple dictionary implemented * using composable states. *
\******************************************************************************/
// This file is partially included in the manual, do not modify
// without updating the references in the *.tex files!
// Manual references: lines 22-44 (Actor.tex)
#include <string>
#include <iostream>
#include <unordered_map>
#include "caf/all.hpp"
using std::cout;
using std::endl;
using std::string;
using namespace caf;
namespace {
using dict = typed_actor<reacts_to<put_atom, string, string>,
replies_to<get_atom, string>::with<string>>;
class dict_behavior : public composable_behavior<dict> {
public:
result<string> operator()(get_atom, param<string> key) override {
auto i = values_.find(key);
if (i == values_.end())
return "";
return i->second;
}
result<void> operator()(put_atom, param<string> key,
param<string> value) override {
if (values_.count(key) != 0)
return unit;
values_.emplace(key.move(), value.move());
return unit;
}
protected:
std::unordered_map<string, string> values_;
};
} // namespace
void caf_main(actor_system& system) {
auto f = make_function_view(system.spawn<dict_behavior>());
f(put_atom_v, "CAF", "success");
cout << "CAF is the key to " << f(get_atom_v, "CAF") << endl;
}
CAF_MAIN()
...@@ -26,7 +26,6 @@ add_enum_consistency_check("caf/intrusive/task_result.hpp" ...@@ -26,7 +26,6 @@ add_enum_consistency_check("caf/intrusive/task_result.hpp"
set(CAF_CORE_SOURCES set(CAF_CORE_SOURCES
src/abstract_actor.cpp src/abstract_actor.cpp
src/abstract_channel.cpp src/abstract_channel.cpp
src/abstract_composable_behavior.cpp
src/abstract_group.cpp src/abstract_group.cpp
src/actor.cpp src/actor.cpp
src/actor_addr.cpp src/actor_addr.cpp
...@@ -178,7 +177,6 @@ set(CAF_CORE_TEST_SOURCES ...@@ -178,7 +177,6 @@ set(CAF_CORE_TEST_SOURCES
test/blocking_actor.cpp test/blocking_actor.cpp
test/broadcast_downstream_manager.cpp test/broadcast_downstream_manager.cpp
test/byte.cpp test/byte.cpp
test/composable_behavior.cpp
test/composition.cpp test/composition.cpp
test/config_option.cpp test/config_option.cpp
test/config_option_set.cpp test/config_option_set.cpp
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <utility>
#include "caf/detail/core_export.hpp"
#include "caf/fwd.hpp"
namespace caf {
/// Marker type that allows CAF to spawn actors from composable states.
class CAF_CORE_EXPORT abstract_composable_behavior {
public:
virtual ~abstract_composable_behavior();
virtual void init_behavior(message_handler& x) = 0;
};
} // namespace caf
...@@ -35,7 +35,6 @@ ...@@ -35,7 +35,6 @@
#include "caf/actor_profiler.hpp" #include "caf/actor_profiler.hpp"
#include "caf/actor_registry.hpp" #include "caf/actor_registry.hpp"
#include "caf/actor_traits.hpp" #include "caf/actor_traits.hpp"
#include "caf/composable_behavior_based_actor.hpp"
#include "caf/detail/core_export.hpp" #include "caf/detail/core_export.hpp"
#include "caf/detail/init_fun_factory.hpp" #include "caf/detail/init_fun_factory.hpp"
#include "caf/detail/spawn_fwd.hpp" #include "caf/detail/spawn_fwd.hpp"
...@@ -301,11 +300,6 @@ public: ...@@ -301,11 +300,6 @@ public:
return spawn_impl<C, Os>(cfg, detail::spawn_fwd<Ts>(xs)...); return spawn_impl<C, Os>(cfg, detail::spawn_fwd<Ts>(xs)...);
} }
template <class S, spawn_options Os = no_spawn_options>
infer_handle_from_state_t<S> spawn() {
return spawn<composable_behavior_based_actor<S>, Os>();
}
/// Called by `spawn` when used to create a functor-based actor to select a /// Called by `spawn` when used to create a functor-based actor to select a
/// proper implementation and then delegates to `spawn_impl`. /// proper implementation and then delegates to `spawn_impl`.
/// @param cfg To-be-filled config for the actor. /// @param cfg To-be-filled config for the actor.
......
...@@ -22,7 +22,6 @@ ...@@ -22,7 +22,6 @@
#include "caf/abstract_actor.hpp" #include "caf/abstract_actor.hpp"
#include "caf/abstract_channel.hpp" #include "caf/abstract_channel.hpp"
#include "caf/abstract_composable_behavior.hpp"
#include "caf/abstract_group.hpp" #include "caf/abstract_group.hpp"
#include "caf/actor.hpp" #include "caf/actor.hpp"
#include "caf/actor_addr.hpp" #include "caf/actor_addr.hpp"
...@@ -46,8 +45,6 @@ ...@@ -46,8 +45,6 @@
#include "caf/binary_serializer.hpp" #include "caf/binary_serializer.hpp"
#include "caf/blocking_actor.hpp" #include "caf/blocking_actor.hpp"
#include "caf/byte_buffer.hpp" #include "caf/byte_buffer.hpp"
#include "caf/composable_behavior.hpp"
#include "caf/composed_behavior.hpp"
#include "caf/config_option.hpp" #include "caf/config_option.hpp"
#include "caf/config_option_adder.hpp" #include "caf/config_option_adder.hpp"
#include "caf/config_value.hpp" #include "caf/config_value.hpp"
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/abstract_composable_behavior.hpp"
#include "caf/behavior.hpp"
#include "caf/param.hpp"
#include "caf/replies_to.hpp"
#include "caf/typed_actor.hpp"
#include "caf/typed_actor_pointer.hpp"
namespace caf {
/// Generates an interface class that provides `operator()`. The signature
/// of the apply operator is derived from the typed message passing interface
/// `MPI`.
template <class MPI>
class composable_behavior_base;
template <class... Xs, class... Ys>
class composable_behavior_base<
typed_mpi<detail::type_list<Xs...>, output_tuple<Ys...>>> {
public:
virtual ~composable_behavior_base() noexcept {
// nop
}
virtual result<Ys...> operator()(param_t<Xs>...) = 0;
auto make_callback() {
return [this](param_t<Xs>... xs) { return (*this)(std::move(xs)...); };
}
};
/// Base type for composable actor states.
template <class TypedActor>
class composable_behavior;
template <class... Clauses>
class composable_behavior<typed_actor<Clauses...>>
: virtual public abstract_composable_behavior,
public composable_behavior_base<Clauses>... {
public:
using signatures = detail::type_list<Clauses...>;
using handle_type = typename detail::tl_apply<signatures, typed_actor>::type;
using actor_base = typename handle_type::base;
using broker_base = typename handle_type::broker_base;
using behavior_type = typename handle_type::behavior_type;
composable_behavior() : self(nullptr) {
// nop
}
template <class SelfPointer>
unit_t init_selfptr(SelfPointer x) {
CAF_ASSERT(x != nullptr);
self = x;
return unit;
}
void init_behavior(message_handler& x) override {
init_behavior_impl(x);
}
unit_t init_behavior_impl(message_handler& x) {
if (x)
x = x.or_else(composable_behavior_base<Clauses>::make_callback()...);
else
x.assign(composable_behavior_base<Clauses>::make_callback()...);
return unit;
}
typed_actor_pointer<Clauses...> self;
};
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/message_handler.hpp"
#include "caf/stateful_actor.hpp"
namespace caf {
/// Implementation class for spawning composable states directly as actors.
template <class State, class Base = typename State::actor_base>
class composable_behavior_based_actor : public stateful_actor<State, Base> {
public:
static_assert(!std::is_abstract<State>::value,
"State is abstract, please make sure to override all "
"virtual operator() member functions");
using super = stateful_actor<State, Base>;
template <class... Ts>
explicit composable_behavior_based_actor(actor_config& cfg, Ts&&... xs)
: super(cfg, std::forward<Ts>(xs)...) {
// nop
}
using behavior_type = typename State::behavior_type;
behavior_type make_behavior() override {
this->state.init_selfptr(this);
message_handler tmp;
this->state.init_behavior(tmp);
return behavior_type{typename behavior_type::unsafe_init{}, std::move(tmp)};
}
};
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/composable_behavior.hpp"
#include "caf/param.hpp"
#include "caf/typed_actor_pointer.hpp"
namespace caf {
template <class... Ts>
class composed_behavior : public Ts... {
public:
using signatures =
typename detail::tl_union<typename Ts::signatures...>::type;
using handle_type = typename detail::tl_apply<signatures, typed_actor>::type;
using behavior_type = typename handle_type::behavior_type;
using actor_base = typename handle_type::base;
using broker_base = typename handle_type::broker_base;
using self_pointer = typename handle_type::pointer_view;
composed_behavior() : self(nullptr) {
// nop
}
template <class SelfPointer>
unit_t init_selfptr(SelfPointer x) {
CAF_ASSERT(x != nullptr);
self = x;
return unit(static_cast<Ts*>(this)->init_selfptr(x)...);
}
void init_behavior(message_handler& x) override {
init_behavior_impl(x);
}
unit_t init_behavior_impl(message_handler& x) {
return unit(static_cast<Ts*>(this)->init_behavior_impl(x)...);
}
protected:
self_pointer self;
};
} // namespace caf
...@@ -20,14 +20,12 @@ ...@@ -20,14 +20,12 @@
#include <type_traits> #include <type_traits>
#include "caf/detail/implicit_conversions.hpp"
#include "caf/expected.hpp" #include "caf/expected.hpp"
#include "caf/fwd.hpp" #include "caf/fwd.hpp"
#include "caf/optional.hpp" #include "caf/optional.hpp"
#include "caf/param.hpp"
#include "caf/replies_to.hpp" #include "caf/replies_to.hpp"
#include "caf/detail/implicit_conversions.hpp"
namespace caf { namespace caf {
namespace detail { namespace detail {
...@@ -39,35 +37,35 @@ struct dmi; ...@@ -39,35 +37,35 @@ struct dmi;
// case #1: function returning a single value // case #1: function returning a single value
template <class Y, class... Xs> template <class Y, class... Xs>
struct dmi<Y(Xs...)> { struct dmi<Y(Xs...)> {
using type = typed_mpi<type_list<typename param_decay<Xs>::type...>, using type = typed_mpi<type_list<std::decay_t<Xs>...>,
output_tuple<implicit_conversions_t<Y>>>; output_tuple<implicit_conversions_t<Y>>>;
}; };
// case #2a: function returning a result<...> // case #2a: function returning a result<...>
template <class... Ys, class... Xs> template <class... Ys, class... Xs>
struct dmi<result<Ys...>(Xs...)> { struct dmi<result<Ys...>(Xs...)> {
using type = typed_mpi<type_list<typename param_decay<Xs>::type...>, using type = typed_mpi<type_list<std::decay_t<Xs>...>,
output_tuple<implicit_conversions_t<Ys>...>>; output_tuple<implicit_conversions_t<Ys>...>>;
}; };
// case #2b: function returning a std::tuple<...> // case #2b: function returning a std::tuple<...>
template <class... Ys, class... Xs> template <class... Ys, class... Xs>
struct dmi<std::tuple<Ys...>(Xs...)> { struct dmi<std::tuple<Ys...>(Xs...)> {
using type = typed_mpi<type_list<typename param_decay<Xs>::type...>, using type = typed_mpi<type_list<std::decay_t<Xs>...>,
output_tuple<implicit_conversions_t<Ys>...>>; output_tuple<implicit_conversions_t<Ys>...>>;
}; };
// case #2c: function returning a std::tuple<...> // case #2c: function returning a std::tuple<...>
template <class... Ys, class... Xs> template <class... Ys, class... Xs>
struct dmi<delegated<Ys...>(Xs...)> { struct dmi<delegated<Ys...>(Xs...)> {
using type = typed_mpi<type_list<typename param_decay<Xs>::type...>, using type = typed_mpi<type_list<std::decay_t<Xs>...>,
output_tuple<implicit_conversions_t<Ys>...>>; output_tuple<implicit_conversions_t<Ys>...>>;
}; };
// case #2d: function returning a typed_response_promise<...> // case #2d: function returning a typed_response_promise<...>
template <class... Ys, class... Xs> template <class... Ys, class... Xs>
struct dmi<typed_response_promise<Ys...>(Xs...)> { struct dmi<typed_response_promise<Ys...>(Xs...)> {
using type = typed_mpi<type_list<typename param_decay<Xs>::type...>, using type = typed_mpi<type_list<std::decay_t<Xs>...>,
output_tuple<implicit_conversions_t<Ys>...>>; output_tuple<implicit_conversions_t<Ys>...>>;
}; };
...@@ -110,8 +108,7 @@ struct dmfou<timeout_definition<T>, true> { ...@@ -110,8 +108,7 @@ struct dmfou<timeout_definition<T>, true> {
/// Deduces the message passing interface from a function object. /// Deduces the message passing interface from a function object.
template <class T> template <class T>
using deduce_mpi_t = using deduce_mpi_t = typename detail::dmfou<std::decay_t<T>>::type;
typename detail::dmfou<typename param_decay<T>::type>::type;
} // namespace caf } // namespace caf
...@@ -27,7 +27,6 @@ ...@@ -27,7 +27,6 @@
#include "caf/detail/core_export.hpp" #include "caf/detail/core_export.hpp"
#include "caf/detail/int_list.hpp" #include "caf/detail/int_list.hpp"
#include "caf/detail/invoke_result_visitor.hpp" #include "caf/detail/invoke_result_visitor.hpp"
#include "caf/detail/param_message_view.hpp"
#include "caf/detail/tail_argument_token.hpp" #include "caf/detail/tail_argument_token.hpp"
#include "caf/detail/type_traits.hpp" #include "caf/detail/type_traits.hpp"
#include "caf/intrusive_ptr.hpp" #include "caf/intrusive_ptr.hpp"
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/message.hpp"
#include "caf/param.hpp"
namespace caf::detail {
template <class... Ts>
class param_message_view {
public:
explicit param_message_view(const message& msg) noexcept : ptr_(msg.cptr()) {
// nop
}
param_message_view() = delete;
param_message_view(const param_message_view&) noexcept = default;
param_message_view& operator=(const param_message_view&) noexcept = default;
const detail::message_data* operator->() const noexcept {
return ptr_;
}
private:
const detail::message_data* ptr_;
};
template <size_t Index, class... Ts>
auto get(const param_message_view<Ts...>& xs) {
static_assert(Index < sizeof...(Ts));
using type = caf::detail::tl_at_t<caf::detail::type_list<Ts...>, Index>;
return param<type>{xs->storage() + detail::offset_at<Index, Ts...>,
!xs->unique()};
}
} // namespace caf::detail
...@@ -30,7 +30,6 @@ ...@@ -30,7 +30,6 @@
#include "caf/detail/is_one_of.hpp" #include "caf/detail/is_one_of.hpp"
#include "caf/detail/type_list.hpp" #include "caf/detail/type_list.hpp"
#include "caf/fwd.hpp" #include "caf/fwd.hpp"
#include "caf/param.hpp"
#include "caf/timestamp.hpp" #include "caf/timestamp.hpp"
#define CAF_HAS_MEMBER_TRAIT(name) \ #define CAF_HAS_MEMBER_TRAIT(name) \
...@@ -261,8 +260,8 @@ struct callable_trait<R (Ts...)> { ...@@ -261,8 +260,8 @@ struct callable_trait<R (Ts...)> {
/// The unmodified argument types of the function. /// The unmodified argument types of the function.
using arg_types = type_list<Ts...>; using arg_types = type_list<Ts...>;
/// The argument types of the function without CV qualifiers or ::param. /// The argument types of the function without CV qualifiers.
using decayed_arg_types = type_list<param_decay_t<Ts>...>; using decayed_arg_types = type_list<std::decay_t<Ts>...>;
/// The signature of the function. /// The signature of the function.
using fun_sig = R (Ts...); using fun_sig = R (Ts...);
...@@ -273,14 +272,10 @@ struct callable_trait<R (Ts...)> { ...@@ -273,14 +272,10 @@ struct callable_trait<R (Ts...)> {
/// Tells whether the function takes mutable references as argument. /// Tells whether the function takes mutable references as argument.
static constexpr bool mutates_args = (is_mutable_ref<Ts>::value || ...); static constexpr bool mutates_args = (is_mutable_ref<Ts>::value || ...);
/// Tells whether the function wraps arguments in ::param containers.
static constexpr bool has_param_args = (is_param_v<std::decay_t<Ts>> || ...);
/// Selects a suitable view type for passing a ::message to this function. /// Selects a suitable view type for passing a ::message to this function.
using message_view_type = std::conditional_t< using message_view_type
has_param_args, param_message_view<param_decay_t<Ts>...>, = std::conditional_t<mutates_args, typed_message_view<std::decay_t<Ts>...>,
std::conditional_t<mutates_args, typed_message_view<param_decay_t<Ts>...>, const_typed_message_view<std::decay_t<Ts>...>>;
const_typed_message_view<param_decay_t<Ts>...>>>;
/// Tells the number of arguments of the function. /// Tells the number of arguments of the function.
static constexpr size_t num_args = sizeof...(Ts); static constexpr size_t num_args = sizeof...(Ts);
......
...@@ -266,9 +266,6 @@ namespace detail { ...@@ -266,9 +266,6 @@ namespace detail {
template <class> template <class>
class stream_distribution_tree; class stream_distribution_tree;
template <class...>
class param_message_view;
class abstract_worker; class abstract_worker;
class abstract_worker_hub; class abstract_worker_hub;
class disposer; class disposer;
......
...@@ -18,7 +18,6 @@ ...@@ -18,7 +18,6 @@
#pragma once #pragma once
#include "caf/abstract_composable_behavior.hpp"
#include "caf/actor.hpp" #include "caf/actor.hpp"
#include "caf/actor_addr.hpp" #include "caf/actor_addr.hpp"
#include "caf/typed_behavior.hpp" #include "caf/typed_behavior.hpp"
...@@ -133,30 +132,13 @@ struct infer_handle_from_class { ...@@ -133,30 +132,13 @@ struct infer_handle_from_class {
template <class T> template <class T>
struct infer_handle_from_class<T, false> { struct infer_handle_from_class<T, false> {
// nop; this enables SFINAE for spawn to differentiate between // nop; this enables SFINAE
// spawns using actor classes or composable states
}; };
/// @relates infer_handle_from_class /// @relates infer_handle_from_class
template <class T> template <class T>
using infer_handle_from_class_t = typename infer_handle_from_class<T>::type; using infer_handle_from_class_t = typename infer_handle_from_class<T>::type;
template <class T,
bool = std::is_base_of<abstract_composable_behavior, T>::value>
struct infer_handle_from_state {
using type = typename T::handle_type;
};
template <class T>
struct infer_handle_from_state<T, false> {
// nop; this enables SFINAE for spawn to differentiate between
// spawns using actor classes or composable states
};
/// @relates infer_handle_from_state
template <class T>
using infer_handle_from_state_t = typename infer_handle_from_state<T>::type;
template <class T> template <class T>
struct is_handle : std::false_type {}; struct is_handle : std::false_type {};
......
...@@ -96,13 +96,6 @@ public: ...@@ -96,13 +96,6 @@ public:
cfg, std::forward<Ts>(xs)...)); cfg, std::forward<Ts>(xs)...));
} }
template <class T, spawn_options Os = no_spawn_options>
infer_handle_from_state_t<T> spawn() {
using impl = composable_behavior_based_actor<T>;
actor_config cfg{context(), this};
return eval_opts(Os, system().spawn_class<impl, make_unbound(Os)>(cfg));
}
template <spawn_options Os = no_spawn_options, class F, class... Ts> template <spawn_options Os = no_spawn_options, class F, class... Ts>
infer_handle_from_fun_t<F> spawn(F fun, Ts&&... xs) { infer_handle_from_fun_t<F> spawn(F fun, Ts&&... xs) {
using impl = infer_impl_from_fun_t<F>; using impl = infer_impl_from_fun_t<F>;
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <type_traits>
#include "caf/fwd.hpp"
namespace caf {
/// Represents a message handler parameter of type `T` and
/// guarantees copy-on-write semantics.
template <class T>
class param {
public:
enum flag {
shared_access, // x_ lives in a shared message
exclusive_access, // x_ lives in an unshared message
private_access, // x_ is a copy of the original value
};
param(const void* ptr, bool is_shared)
: x_(reinterpret_cast<T*>(const_cast<void*>(ptr))) {
flag_ = is_shared ? shared_access : exclusive_access;
}
param(const param& other) = delete;
param& operator=(const param& other) = delete;
param(param&& other) : x_(other.x_), flag_(other.flag_) {
other.x_ = nullptr;
}
~param() {
if (flag_ == private_access)
delete x_;
}
const T& get() const {
return *x_;
}
operator const T&() const {
return *x_;
}
const T* operator->() const {
return x_;
}
/// Detaches the value if needed and returns a mutable reference to it.
T& get_mutable() {
if (flag_ == shared_access) {
auto cpy = new T(get());
x_ = cpy;
flag_ = private_access;
}
return *x_;
}
/// Moves the value out of the `param`.
T&& move() {
return std::move(get_mutable());
}
private:
T* x_;
flag flag_;
};
/// Converts `T` to `param<T>` unless `T` is arithmetic, an atom constant, or
/// a stream handshake.
template <class T>
struct add_param
: std::conditional<std::is_arithmetic<T>::value || std::is_empty<T>::value, T,
param<T>> {
// nop
};
template <class T>
struct add_param<stream<T>> {
using type = stream<T>;
};
/// Convenience alias that wraps `T` into `param<T>` unless `T` is arithmetic,
/// a stream handshake or an atom constant.
template <class T>
using param_t = typename add_param<T>::type;
/// Unpacks `param<T>` to `T`.
template <class T>
struct remove_param {
using type = T;
};
template <class T>
struct remove_param<param<T>> {
using type = T;
};
/// Convenience struct for `remove_param<std::decay<T>>`.
template <class T>
struct param_decay {
using type = typename remove_param<typename std::decay<T>::type>::type;
};
/// @relates param_decay
template <class T>
using param_decay_t = typename param_decay<T>::type;
/// Queries whether `T` is a ::param.
template <class T>
struct is_param {
static constexpr bool value = false;
};
template <class T>
struct is_param<param<T>> {
static constexpr bool value = true;
};
/// @relates is_param
template <class T>
constexpr bool is_param_v = is_param<T>::value;
} // namespace caf
...@@ -52,12 +52,6 @@ public: ...@@ -52,12 +52,6 @@ public:
return self_->spawn<T, Os>(std::forward<Ts>(xs)...); return self_->spawn<T, Os>(std::forward<Ts>(xs)...);
} }
/// @copydoc local_actor::spawn
template <class T, spawn_options Os = no_spawn_options>
infer_handle_from_state_t<T> spawn() {
return self_->spawn<T, Os>();
}
/// @copydoc local_actor::spawn /// @copydoc local_actor::spawn
template <spawn_options Os = no_spawn_options, class F, class... Ts> template <spawn_options Os = no_spawn_options, class F, class... Ts>
typename infer_handle_from_fun<F>::type spawn(F fun, Ts&&... xs) { typename infer_handle_from_fun<F>::type spawn(F fun, Ts&&... xs) {
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/abstract_composable_behavior.hpp"
namespace caf {
abstract_composable_behavior::~abstract_composable_behavior() {
// nop
}
} // namespace caf
...@@ -18,18 +18,13 @@ ...@@ -18,18 +18,13 @@
#include "caf/actor_control_block.hpp" #include "caf/actor_control_block.hpp"
#include "caf/to_string.hpp"
#include "caf/message.hpp"
#include "caf/actor_system.hpp"
#include "caf/proxy_registry.hpp"
#include "caf/abstract_actor.hpp" #include "caf/abstract_actor.hpp"
#include "caf/mailbox_element.hpp" #include "caf/actor_system.hpp"
#include "caf/detail/disposer.hpp" #include "caf/detail/disposer.hpp"
#include "caf/mailbox_element.hpp"
#include "caf/message.hpp"
#include "caf/proxy_registry.hpp"
#include "caf/sec.hpp"
namespace caf { namespace caf {
......
...@@ -32,6 +32,8 @@ ...@@ -32,6 +32,8 @@
#include "caf/detail/parser/read_ini.hpp" #include "caf/detail/parser/read_ini.hpp"
#include "caf/detail/parser/read_string.hpp" #include "caf/detail/parser/read_string.hpp"
#include "caf/message_builder.hpp" #include "caf/message_builder.hpp"
#include "caf/pec.hpp"
#include "caf/sec.hpp"
#include "caf/type_id.hpp" #include "caf/type_id.hpp"
namespace caf { namespace caf {
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE composable_behavior
#include "caf/composable_behavior.hpp"
#include "core-test.hpp"
#include "caf/attach_stream_sink.hpp"
#include "caf/attach_stream_source.hpp"
#include "caf/attach_stream_stage.hpp"
#include "caf/composable_behavior_based_actor.hpp"
#include "caf/typed_actor.hpp"
#define ERROR_HANDLER [&](error& err) { CAF_FAIL(system.render(err)); }
using namespace caf;
namespace {
using i3_actor = typed_actor<replies_to<int, int, int>::with<int>>;
using d_actor = typed_actor<replies_to<double>::with<double, double>>;
using source_actor = typed_actor<replies_to<open_atom>::with<stream<int>>>;
using stage_actor = typed_actor<replies_to<stream<int>>::with<stream<int>>>;
using sink_actor = typed_actor<reacts_to<stream<int>>>;
static_assert(std::is_same<foo_actor, i3_actor::extend_with<d_actor>>::value);
// -- composable behaviors using primitive data types and streams --------------
class foo_actor_state : public composable_behavior<foo_actor> {
public:
result<int> operator()(int x, int y, int z) override {
return x + y + z;
}
result<double, double> operator()(double x) override {
return {x, x};
}
};
class i3_actor_state : public composable_behavior<i3_actor> {
public:
result<int> operator()(int x, int y, int z) override {
return x + y + z;
}
};
class d_actor_state : public composable_behavior<d_actor> {
public:
result<double, double> operator()(double x) override {
return {x, x};
}
};
class i3_actor_state2 : public composable_behavior<i3_actor> {
public:
result<int> operator()(int x, int y, int z) override {
return x * (y * z);
}
};
// checks whether CAF resolves "diamonds" properly by inheriting
// from two behaviors that both implement i3_actor
struct foo_actor_state2
: composed_behavior<i3_actor_state2, i3_actor_state, d_actor_state> {
result<int> operator()(int x, int y, int z) override {
return x - y - z;
}
};
class source_actor_state : public composable_behavior<source_actor> {
public:
result<stream<int>> operator()(open_atom) override {
return attach_stream_source(
self, [](size_t& counter) { counter = 0; },
[](size_t& counter, downstream<int>& out, size_t hint) {
auto n = std::min(static_cast<size_t>(100 - counter), hint);
for (size_t i = 0; i < n; ++i)
out.push(counter++);
},
[](const size_t& counter) { return counter < 100; });
}
};
class stage_actor_state : public composable_behavior<stage_actor> {
public:
result<stream<int>> operator()(stream<int> in) override {
return attach_stream_stage(
self, in,
[](unit_t&) {
// nop
},
[](unit_t&, downstream<int>& out, int x) {
if (x % 2 == 0)
out.push(x);
});
}
};
class sink_actor_state : public composable_behavior<sink_actor> {
public:
std::vector<int> buf;
result<void> operator()(stream<int> in) override {
attach_stream_sink(
self, in,
[](unit_t&) {
// nop
},
[=](unit_t&, int x) { buf.emplace_back(x); });
return unit;
}
};
// -- composable behaviors using param<T> arguments ----------------------------
std::atomic<long> counting_strings_created;
std::atomic<long> counting_strings_moved;
std::atomic<long> counting_strings_destroyed;
} // namespace
// counts how many instances where created
counting_string::counting_string() {
++counting_strings_created;
}
counting_string::counting_string(const char* cstr) : str_(cstr) {
++counting_strings_created;
}
counting_string::counting_string(const counting_string& x) : str_(x.str_) {
++counting_strings_created;
}
counting_string::counting_string(counting_string&& x)
: str_(std::move(x.str_)) {
++counting_strings_created;
++counting_strings_moved;
}
counting_string::~counting_string() {
++counting_strings_destroyed;
}
bool operator==(const counting_string& x, const counting_string& y) {
return x.str() == y.str();
}
bool operator==(const counting_string& x, const char* y) {
return x.str() == y;
}
std::string to_string(const counting_string& ref) {
return ref.str();
}
namespace std {
template <>
struct hash<counting_string> {
size_t operator()(const counting_string& ref) const {
hash<string> f;
return f(ref.str());
}
};
} // namespace std
namespace {
// a simple dictionary
using dict = typed_actor<
replies_to<get_atom, counting_string>::with<counting_string>,
replies_to<put_atom, counting_string, counting_string>::with<void>>;
class dict_state : public composable_behavior<dict> {
public:
result<counting_string> operator()(get_atom,
param<counting_string> key) override {
auto i = values_.find(key.get());
if (i == values_.end())
return "";
return i->second;
}
result<void> operator()(put_atom, param<counting_string> key,
param<counting_string> value) override {
if (values_.count(key.get()) != 0)
return unit;
values_.emplace(key.move(), value.move());
return unit;
}
protected:
std::unordered_map<counting_string, counting_string> values_;
};
using delayed_testee_actor = typed_actor<
reacts_to<int>, replies_to<bool>::with<int>, reacts_to<std::string>>;
class delayed_testee : public composable_behavior<delayed_testee_actor> {
public:
result<void> operator()(int x) override {
CAF_CHECK_EQUAL(x, 42);
delayed_anon_send(self, std::chrono::milliseconds(10), true);
return unit;
}
result<int> operator()(bool x) override {
CAF_CHECK_EQUAL(x, true);
self->delayed_send(self, std::chrono::milliseconds(10), "hello");
return 0;
}
result<void> operator()(param<std::string> x) override {
CAF_CHECK_EQUAL(x.get(), "hello");
return unit;
}
};
struct config : actor_system_config {
config() {
using foo_actor_impl = composable_behavior_based_actor<foo_actor_state>;
add_actor_type<foo_actor_impl>("foo_actor");
}
};
struct fixture : test_coordinator_fixture<config> {
// nop
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(composable_behaviors_tests, fixture)
CAF_TEST(composition) {
CAF_MESSAGE("test foo_actor_state");
auto f1 = sys.spawn<foo_actor_state>();
inject((int, int, int), from(self).to(f1).with(1, 2, 4));
expect((int), from(f1).to(self).with(7));
inject((double), from(self).to(f1).with(42.0));
expect((double, double), from(f1).to(self).with(42.0, 42.0));
CAF_MESSAGE("test composed_behavior<i3_actor_state, d_actor_state>");
f1 = sys.spawn<composed_behavior<i3_actor_state, d_actor_state>>();
inject((int, int, int), from(self).to(f1).with(1, 2, 4));
expect((int), from(f1).to(self).with(7));
inject((double), from(self).to(f1).with(42.0));
expect((double, double), from(f1).to(self).with(42.0, 42.0));
CAF_MESSAGE("test composed_behavior<i3_actor_state2, d_actor_state>");
f1 = sys.spawn<composed_behavior<i3_actor_state2, d_actor_state>>();
inject((int, int, int), from(self).to(f1).with(1, 2, 4));
expect((int), from(f1).to(self).with(8));
inject((double), from(self).to(f1).with(42.0));
expect((double, double), from(f1).to(self).with(42.0, 42.0));
CAF_MESSAGE("test foo_actor_state2");
f1 = sys.spawn<foo_actor_state2>();
inject((int, int, int), from(self).to(f1).with(1, 2, 4));
expect((int), from(f1).to(self).with(-5));
inject((double), from(self).to(f1).with(42.0));
expect((double, double), from(f1).to(self).with(42.0, 42.0));
}
CAF_TEST(param_detaching) {
auto dict = actor_cast<actor>(sys.spawn<dict_state>());
// Using CAF is the key to success!
counting_string key{"CAF"};
counting_string value{"success"};
CAF_CHECK_EQUAL(counting_strings_created.load(), 2);
CAF_CHECK_EQUAL(counting_strings_moved.load(), 0);
CAF_CHECK_EQUAL(counting_strings_destroyed.load(), 0);
// Wrap two strings into messages.
auto put_msg = make_message(put_atom_v, key, value);
CAF_CHECK_EQUAL(put_msg.cptr()->get_reference_count(), 1u);
auto get_msg = make_message(get_atom_v, key);
CAF_CHECK_EQUAL(get_msg.cptr()->get_reference_count(), 1u);
CAF_CHECK_EQUAL(counting_strings_created.load(), 5);
CAF_CHECK_EQUAL(counting_strings_moved.load(), 0);
CAF_CHECK_EQUAL(counting_strings_destroyed.load(), 0);
// Send put message to dictionary.
self->send(dict, put_msg);
CAF_CHECK_EQUAL(put_msg.cptr()->get_reference_count(), 2u);
sched.run();
CAF_CHECK_EQUAL(put_msg.cptr()->get_reference_count(), 1u);
// The handler of put_atom calls .move() on key and value, both causing to
// detach + move into the map.
CAF_CHECK_EQUAL(counting_strings_created.load(), 9);
CAF_CHECK_EQUAL(counting_strings_moved.load(), 2);
CAF_CHECK_EQUAL(counting_strings_destroyed.load(), 2);
// Send put message to dictionary again.
self->send(dict, put_msg);
CAF_CHECK_EQUAL(put_msg.cptr()->get_reference_count(), 2u);
sched.run();
CAF_CHECK_EQUAL(put_msg.cptr()->get_reference_count(), 1u);
// The handler checks whether key already exists -> no copies.
CAF_CHECK_EQUAL(counting_strings_created.load(), 9);
CAF_CHECK_EQUAL(counting_strings_moved.load(), 2);
CAF_CHECK_EQUAL(counting_strings_destroyed.load(), 2);
// Alter our initial put, this time moving it to the dictionary.
put_msg.get_mutable_as<counting_string>(1) = "neverlord";
put_msg.get_mutable_as<counting_string>(2) = "CAF";
// Send new put message to dictionary.
self->send(dict, std::move(put_msg));
CAF_CHECK(!put_msg);
CAF_CHECK_EQUAL(counting_strings_created.load(), 9);
CAF_CHECK_EQUAL(counting_strings_moved.load(), 2);
CAF_CHECK_EQUAL(counting_strings_destroyed.load(), 2);
sched.run();
// The handler of put_atom calls .move() on key and value, but no detaching
// occurs this time (unique access) -> move into the map.
CAF_CHECK_EQUAL(counting_strings_created.load(), 11);
CAF_CHECK_EQUAL(counting_strings_moved.load(), 4);
CAF_CHECK_EQUAL(counting_strings_destroyed.load(), 4);
// Finally, check for original key.
self->send(dict, std::move(get_msg));
CAF_CHECK(!get_msg);
sched.run();
self->receive([&](const counting_string& str) {
// We receive a copy of the value, which is copied out of the map and then
// moved into the result message; the string from our get_msg is destroyed.
CAF_CHECK_EQUAL(counting_strings_created.load(), 13);
CAF_CHECK_EQUAL(counting_strings_moved.load(), 5);
CAF_CHECK_EQUAL(counting_strings_destroyed.load(), 6);
CAF_CHECK_EQUAL(str, "success");
});
// Temporary of our handler is destroyed.
CAF_CHECK_EQUAL(counting_strings_destroyed.load(), 7);
self->send_exit(dict, exit_reason::user_shutdown);
sched.run();
dict = nullptr;
// Only `key` and `value` from this scope remain.
CAF_CHECK_EQUAL(counting_strings_destroyed.load(), 11);
}
CAF_TEST(delayed_sends) {
auto testee = self->spawn<delayed_testee>();
inject((int), from(self).to(testee).with(42));
disallow((bool), from(_).to(testee));
sched.trigger_timeouts();
expect((bool), from(_).to(testee));
disallow((std::string), from(testee).to(testee).with("hello"));
sched.trigger_timeouts();
expect((std::string), from(testee).to(testee).with("hello"));
}
CAF_TEST(dynamic_spawning) {
auto testee = unbox(sys.spawn<foo_actor>("foo_actor", make_message()));
inject((int, int, int), from(self).to(testee).with(1, 2, 4));
expect((int), from(testee).to(self).with(7));
inject((double), from(self).to(testee).with(42.0));
expect((double, double), from(testee).to(self).with(42.0, 42.0));
}
CAF_TEST(streaming) {
auto src = sys.spawn<source_actor_state>();
auto stg = sys.spawn<stage_actor_state>();
auto snk = sys.spawn<sink_actor_state>();
using src_to_stg = typed_actor<replies_to<open_atom>::with<stream<int>>>;
using stg_to_snk = typed_actor<reacts_to<stream<int>>>;
static_assert(std::is_same<decltype(stg * src), src_to_stg>::value,
"stg * src produces the wrong type");
static_assert(std::is_same<decltype(snk * stg), stg_to_snk>::value,
"stg * src produces the wrong type");
auto pipeline = snk * stg * src;
self->send(pipeline, open_atom_v);
run();
using sink_actor = composable_behavior_based_actor<sink_actor_state>;
auto& st = deref<sink_actor>(snk).state;
CAF_CHECK_EQUAL(st.buf.size(), 50u);
auto is_even = [](int x) { return x % 2 == 0; };
CAF_CHECK(std::all_of(st.buf.begin(), st.buf.end(), is_even));
anon_send_exit(src, exit_reason::user_shutdown);
anon_send_exit(stg, exit_reason::user_shutdown);
anon_send_exit(snk, exit_reason::user_shutdown);
}
CAF_TEST_FIXTURE_SCOPE_END()
...@@ -18,39 +18,6 @@ using foo_actor ...@@ -18,39 +18,6 @@ using foo_actor
= caf::typed_actor<caf::replies_to<int32_t, int32_t, int32_t>::with<int32_t>, = caf::typed_actor<caf::replies_to<int32_t, int32_t, int32_t>::with<int32_t>,
caf::replies_to<double>::with<double, double>>; caf::replies_to<double>::with<double, double>>;
// A string that counts how many instances where created. Implemented in
// composable_behavior.cpp.
struct counting_string {
public:
counting_string();
explicit counting_string(const char* cstr);
counting_string(const counting_string& x);
counting_string(counting_string&& x);
~counting_string();
counting_string& operator=(const char* cstr) {
str_ = cstr;
return *this;
}
const std::string& str() const {
return str_;
}
template <class Inspector>
friend typename Inspector::result_type
inspect(Inspector& f, counting_string& x) {
return f(x.str_);
}
private:
std::string str_;
};
// A simple POD type. // A simple POD type.
struct dummy_struct { struct dummy_struct {
int a; int a;
...@@ -275,7 +242,6 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_test, caf::first_custom_type_id) ...@@ -275,7 +242,6 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_test, caf::first_custom_type_id)
ADD_TYPE_ID((caf::stream<int32_t>) ) ADD_TYPE_ID((caf::stream<int32_t>) )
ADD_TYPE_ID((caf::stream<std::string>) ) ADD_TYPE_ID((caf::stream<std::string>) )
ADD_TYPE_ID((caf::stream<std::pair<level, std::string>>) ) ADD_TYPE_ID((caf::stream<std::pair<level, std::string>>) )
ADD_TYPE_ID((counting_string))
ADD_TYPE_ID((dummy_enum)) ADD_TYPE_ID((dummy_enum))
ADD_TYPE_ID((dummy_enum_class)) ADD_TYPE_ID((dummy_enum_class))
ADD_TYPE_ID((dummy_struct)) ADD_TYPE_ID((dummy_struct))
......
...@@ -353,21 +353,17 @@ Class-based Actors ...@@ -353,21 +353,17 @@ Class-based Actors
Implementing an actor using a class requires the following: Implementing an actor using a class requires the following:
* Provide a constructor taking a reference of type ``actor_config&`` as first argument, which is forwarded to the base class. The config is passed implicitly to the constructor when calling ``spawn``, which also forwards any number of additional arguments to the constructor. * Provide a constructor taking a reference of type ``actor_config&`` as first
* Override ``make_behavior`` for event-based actors and ``act`` for blocking actors. argument, which is forwarded to the base class. The config is passed
implicitly to the constructor when calling ``spawn``, which also forwards any
number of additional arguments to the constructor.
* Override ``make_behavior`` for event-based actors and ``act`` for blocking
actors.
Implementing actors with classes works for all kinds of actors and allows Implementing actors with classes works for all kinds of actors and allows
simple management of state via member variables. However, composing states via simple management of state via member variables. However, composing states via
inheritance can get quite tedious. For dynamically typed actors, composing inheritance can get quite tedious. For dynamically typed actors, composing
states is particularly hard, because the compiler cannot provide much help. For states is particularly hard, because the compiler cannot provide much help.
statically typed actors, CAF also provides an API for composable
behaviors composable-behavior_ that works well with inheritance. The
following three examples implement the forward declarations shown in
spawn_.
.. literalinclude:: /examples/message_passing/calculator.cpp
:language: C++
:lines: 58-92
.. _stateful-actor: .. _stateful-actor:
...@@ -395,69 +391,6 @@ function-based_. ...@@ -395,69 +391,6 @@ function-based_.
:language: C++ :language: C++
:lines: 49-50 :lines: 49-50
.. _composable-behavior:
Actors from Composable Behaviors :sup:`experimental`
-----------------------------------------------------
When building larger systems, it is often useful to implement the behavior of
an actor in terms of other, existing behaviors. The composable behaviors in
CAF allow developers to generate a behavior class from a messaging
interface interface_.
The base type for composable behaviors is ``composable_behavior<T>``,
where ``T`` is a ``typed_actor<...>``. CAF maps each
``replies_to<A,B,C>::with<D,E,F>`` in ``T`` to a pure virtual
member function with signature:
.. code-block:: C++
result<D, E, F> operator()(param<A>, param<B>, param<C>);.
Note that ``operator()`` will take integral types as well as atom constants
simply by value. A ``result<T>`` accepts either a value of type ``T``, a
``skip_t`` (see :ref:`default-handler`), an ``error`` (see :ref:`error`), a
``delegated<T>`` (see :ref:`delegate`), or a ``response_promise<T>`` (see
:ref:`promise`). A ``result<void>`` is constructed by returning ``unit``.
A behavior that combines the behaviors ``X``, ``Y``, and
``Z`` must inherit from ``composed_behavior<X,Y,Z>`` instead of
inheriting from the three classes directly. The class
``composed_behavior`` ensures that the behaviors are concatenated
correctly. In case one message handler is defined in multiple base types, the
*first* type in declaration order wins. For example, if ``X`` and
``Y`` both implement the interface
``replies_to<int,int>::with<int>``, only the handler implemented in
``X`` is active.
Any composable (or composed) behavior with no pure virtual member functions can
be spawned directly through an actor system by calling
``system.spawn<...>()``, as shown below.
.. literalinclude:: /examples/composition/calculator_behavior.cpp
:language: C++
:lines: 20-45
The second example illustrates how to use non-primitive values that are wrapped
in a ``param<T>`` when working with composable behaviors. The purpose
of ``param<T>`` is to provide a single interface for both constant and
non-constant access. Constant access is modeled with the implicit conversion
operator to a const reference, the member function ``get()``, and
``operator->``.
When acquiring mutable access to the represented value, CAF copies the value
before allowing mutable access to it if more than one reference to the value
exists. This copy-on-write optimization avoids race conditions by design, while
minimizing copy operations (see :ref:`copy-on-write`). A mutable reference is
returned from the member functions ``get_mutable()`` and ``move()``. The latter
is a convenience function for ``std::move(x.get_mutable())``. The following
example illustrates how to use ``param<std::string>`` when implementing a simple
dictionary.
.. literalinclude:: /examples/composition/dictionary_behavior.cpp
:language: C++
:lines: 22-44
.. _attach: .. _attach:
Attaching Cleanup Code to Actors Attaching Cleanup Code to Actors
......
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