Unverified Commit fdce05dd authored by Dominik Charousset's avatar Dominik Charousset Committed by GitHub

Merge pull request #1073

Enable states with non-default constructor
parents 99212ecc d02e4173
...@@ -68,6 +68,15 @@ is based on [Keep a Changelog](https://keepachangelog.com). ...@@ -68,6 +68,15 @@ is based on [Keep a Changelog](https://keepachangelog.com).
- Our manual now uses `reStructuredText` instead of `LaTeX`. We hope this makes - Our manual now uses `reStructuredText` instead of `LaTeX`. We hope this makes
extending the manual easier and lowers the barrier to entry for new extending the manual easier and lowers the barrier to entry for new
contributors. contributors.
- A `stateful_actor` now forwards excess arguments to the `State` rather than to
the `Base`. This enables states with non-default constructors. When using
`stateful_actor<State>` as pointer type in function-based actors, nothing
changes (i.e. the new API is backwards compatible for this case). However,
calling `spawn<stateful_actor<State>>(xs...)` now initializes the `State` with
the argument pack `xs...` (plus optionally a `self` pointer as first
argument). Furthermore, the state class can now provide a `make_behavior`
member function to initialize the actor (this has no effect for function-based
actors).
### Removed ### Removed
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
#include "caf/function_view.hpp" #include "caf/function_view.hpp"
#include "caf/policy/select_all.hpp" #include "caf/policy/select_all.hpp"
#include "caf/scoped_actor.hpp" #include "caf/scoped_actor.hpp"
#include "caf/stateful_actor.hpp"
#include "caf/typed_actor.hpp" #include "caf/typed_actor.hpp"
#include "caf/typed_event_based_actor.hpp" #include "caf/typed_event_based_actor.hpp"
......
...@@ -33,7 +33,9 @@ public: ...@@ -33,7 +33,9 @@ public:
using super = stateful_actor<State, Base>; using super = stateful_actor<State, Base>;
composable_behavior_based_actor(actor_config& cfg) : super(cfg) { template <class... Ts>
explicit composable_behavior_based_actor(actor_config& cfg, Ts&&... xs)
: super(cfg, std::forward<Ts>(xs)...) {
// nop // nop
} }
......
...@@ -437,6 +437,7 @@ public: ...@@ -437,6 +437,7 @@ public:
CAF_HAS_MEMBER_TRAIT(size); CAF_HAS_MEMBER_TRAIT(size);
CAF_HAS_MEMBER_TRAIT(data); CAF_HAS_MEMBER_TRAIT(data);
CAF_HAS_MEMBER_TRAIT(make_behavior);
/// Checks whether F is convertible to either `std::function<void (T&)>` /// Checks whether F is convertible to either `std::function<void (T&)>`
/// or `std::function<void (const T&)>`. /// or `std::function<void (const T&)>`.
......
...@@ -21,7 +21,6 @@ ...@@ -21,7 +21,6 @@
#include "caf/abstract_composable_behavior.hpp" #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/stateful_actor.hpp"
#include "caf/typed_behavior.hpp" #include "caf/typed_behavior.hpp"
namespace caf { namespace caf {
......
...@@ -24,88 +24,87 @@ ...@@ -24,88 +24,87 @@
#include "caf/fwd.hpp" #include "caf/fwd.hpp"
#include "caf/sec.hpp" #include "caf/sec.hpp"
#include "caf/logger.hpp"
#include "caf/detail/type_traits.hpp" #include "caf/detail/type_traits.hpp"
namespace caf { namespace caf::detail {
/// An event-based actor with managed state. The state is constructed /// Conditional base type for `stateful_actor` that overrides `make_behavior` if
/// before `make_behavior` will get called and destroyed after the /// `State::make_behavior()` exists.
/// actor called `quit`. This state management brakes cycles and template <class State, class Base>
/// allows actors to automatically release resources as soon class stateful_actor_base : public Base {
/// as possible.
template <class State, class Base /* = event_based_actor (see fwd.hpp) */>
class stateful_actor : public Base {
public: public:
template <class... Ts> using Base::Base;
explicit stateful_actor(actor_config& cfg, Ts&&... xs)
: Base(cfg, std::forward<Ts>(xs)...), state(state_) {
cr_state(this);
}
~stateful_actor() override {
// nop
}
/// Destroys the state of this actor (no further overriding allowed).
void on_exit() final {
CAF_LOG_TRACE("");
state_.~State();
}
const char* name() const final {
return get_name(state_);
}
/// A reference to the actor's state.
State& state;
/// @cond PRIVATE typename Base::behavior_type make_behavior() override;
};
void initialize() override { /// Evaluates to either `stateful_actor_base<State, Base> `or `Base`, depending
Base::initialize(); /// on whether `State::make_behavior()` exists.
} template <class State, class Base>
using stateful_actor_base_t
= std::conditional_t<has_make_behavior_member<State>::value,
stateful_actor_base<State, Base>, Base>;
/// @endcond } // namespace caf::detail
private: namespace caf {
template <class T>
typename std::enable_if<std::is_constructible<State, T>::value>::type
cr_state(T arg) {
new (&state_) State(arg);
}
template <class T> /// An event-based actor with managed state. The state is constructed with the
typename std::enable_if<!std::is_constructible<State, T>::value>::type /// actor, but destroyed when the actor calls `quit`. This state management
cr_state(T) { /// brakes cycles and allows actors to automatically release resources as soon
new (&state_) State(); /// as possible.
} template <class State, class Base /* = event_based_actor (see fwd.hpp) */>
class stateful_actor : public detail::stateful_actor_base_t<State, Base> {
public:
using super = detail::stateful_actor_base_t<State, Base>;
static const char* unbox_str(const char* str) { template <class... Ts>
return str; explicit stateful_actor(actor_config& cfg, Ts&&... xs) : super(cfg) {
if constexpr (std::is_constructible<State, Ts&&...>::value)
new (&state) State(std::forward<Ts>(xs)...);
else
new (&state) State(this, std::forward<Ts>(xs)...);
} }
template <class U> ~stateful_actor() override {
static const char* unbox_str(const U& str) { // nop
return str.c_str();
} }
template <class U> /// @copydoc local_actor::on_exit
typename std::enable_if<detail::has_name<U>::value, const char*>::type /// @note when overriding this member function, make sure to call
get_name(const U& st) const { /// `super::on_exit()` in order to clean up the state.
return unbox_str(st.name); void on_exit() override {
state.~State();
} }
template <class U> const char* name() const override {
typename std::enable_if<!detail::has_name<U>::value, const char*>::type if constexpr (detail::has_name<State>::value) {
get_name(const U&) const { if constexpr (std::is_convertible<decltype(state.name),
const char*>::value)
return state.name;
else
return state.name.c_str();
} else {
return Base::name(); return Base::name();
} }
}
union { union {
State state_; /// The actor's state. This member lives inside a union since its lifetime
/// ends when the actor terminates while the actual actor object lives until
/// its reference count drops to zero.
State state;
}; };
}; };
} // namespace caf } // namespace caf
namespace caf::detail {
template <class State, class Base>
typename Base::behavior_type stateful_actor_base<State, Base>::make_behavior() {
auto dptr = static_cast<stateful_actor<State, Base>*>(this);
return dptr->state.make_behavior();
}
} // namespace caf::detail
...@@ -36,6 +36,12 @@ ...@@ -36,6 +36,12 @@
#define CAF_MSG_TYPE_ADD_ATOM(name) \ #define CAF_MSG_TYPE_ADD_ATOM(name) \
struct name {}; \ struct name {}; \
[[maybe_unused]] constexpr bool operator==(name, name) { \
return true; \
} \
[[maybe_unused]] constexpr bool operator!=(name, name) { \
return false; \
} \
template <class Inspector> \ template <class Inspector> \
auto inspect(Inspector& f, name&) { \ auto inspect(Inspector& f, name&) { \
return f(meta::type_name("caf::" #name)); \ return f(meta::type_name("caf::" #name)); \
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#pragma once #pragma once
#include "caf/detail/type_list.hpp" #include "caf/detail/type_list.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/typed_actor_view.hpp" #include "caf/typed_actor_view.hpp"
namespace caf { namespace caf {
...@@ -33,22 +34,54 @@ public: ...@@ -33,22 +34,54 @@ public:
// nop // nop
} }
template <class Supertype> template <class Supertype,
explicit typed_actor_pointer(Supertype* selfptr) : view_(selfptr) { class = detail::enable_if_t< //
using namespace caf::detail; detail::tl_subset_of<detail::type_list<Sigs...>,
static_assert( typename Supertype::signatures>::value>>
tl_subset_of<type_list<Sigs...>, typename Supertype::signatures>::value, typed_actor_pointer(Supertype* selfptr) : view_(selfptr) {
"cannot create a pointer view to an unrelated actor type"); // nop
} }
explicit typed_actor_pointer(std::nullptr_t) : view_(nullptr) { template <class... OtherSigs,
class = detail::enable_if_t< //
detail::tl_subset_of<detail::type_list<Sigs...>,
detail::type_list<OtherSigs...>>::value>>
typed_actor_pointer(typed_actor_pointer<OtherSigs...> other)
: view_(other.internal_ptr()) {
// nop // nop
} }
typed_actor_pointer(const typed_actor_pointer&) = default; typed_actor_pointer(const typed_actor_pointer&) = default;
explicit typed_actor_pointer(std::nullptr_t) : view_(nullptr) {
// nop
}
typed_actor_pointer& operator=(const typed_actor_pointer&) = default; typed_actor_pointer& operator=(const typed_actor_pointer&) = default;
template <class Supertype>
typed_actor_pointer& operator=(Supertype* ptr) {
using namespace detail;
static_assert(
tl_subset_of<type_list<Sigs...>, typename Supertype::signatures>::value,
"cannot assign pointer of unrelated actor type");
view_.reset(ptr);
return *this;
}
template <class... OtherSigs,
class = detail::enable_if_t< //
detail::tl_subset_of<detail::type_list<Sigs...>,
detail::type_list<OtherSigs...>>::value>>
typed_actor_pointer& operator=(typed_actor_pointer<OtherSigs...> other) {
using namespace detail;
static_assert(
tl_subset_of<type_list<Sigs...>, type_list<OtherSigs...>>::value,
"cannot assign pointer of unrelated actor type");
view_.reset(other.internal_ptr());
return *this;
}
typed_actor_view<Sigs...>* operator->() { typed_actor_view<Sigs...>* operator->() {
return &view_; return &view_;
} }
...@@ -75,16 +108,6 @@ public: ...@@ -75,16 +108,6 @@ public:
return view_.internal_ptr(); return view_.internal_ptr();
} }
template <class Supertype>
typed_actor_pointer& operator=(Supertype* ptr) {
using namespace caf::detail;
static_assert(
tl_subset_of<type_list<Sigs...>, typename Supertype::signatures>::value,
"cannot assign pointer of unrelated actor type");
view_ = ptr;
return *this;
}
private: private:
typed_actor_view<Sigs...> view_; typed_actor_view<Sigs...> view_;
}; };
......
...@@ -31,30 +31,24 @@ namespace caf { ...@@ -31,30 +31,24 @@ namespace caf {
/// Decorates a pointer to a @ref scheduled_actor with a statically typed actor /// Decorates a pointer to a @ref scheduled_actor with a statically typed actor
/// interface. /// interface.
template <class... Sigs> template <class... Sigs>
class typed_actor_view : public extend<typed_actor_view_base, class typed_actor_view
typed_actor_view<Sigs...>>::template : public extend<typed_actor_view_base, typed_actor_view<Sigs...>>::
with<mixin::sender, mixin::requester> { template with<mixin::sender, mixin::requester> {
public: public:
/// Stores the template parameter pack. /// Stores the template parameter pack.
using signatures = detail::type_list<Sigs...>; using signatures = detail::type_list<Sigs...>;
using pointer = scheduled_actor*; using pointer = scheduled_actor*;
typed_actor_view(scheduled_actor* ptr) : self_(ptr) { explicit typed_actor_view(scheduled_actor* ptr) : self_(ptr) {
// nop // nop
} }
typed_actor_view& operator=(scheduled_actor* ptr) {
self_ = ptr;
return *this;
}
// -- spawn functions -------------------------------------------------------- // -- spawn functions --------------------------------------------------------
/// @copydoc local_actor::spawn /// @copydoc local_actor::spawn
template <class T, spawn_options Os = no_spawn_options, class... Ts> template <class T, spawn_options Os = no_spawn_options, class... Ts>
typename infer_handle_from_class<T>::type typename infer_handle_from_class<T>::type spawn(Ts&&... xs) {
spawn(Ts&&... xs) {
return self_->spawn<T, Os>(std::forward<Ts>(xs)...); return self_->spawn<T, Os>(std::forward<Ts>(xs)...);
} }
...@@ -66,8 +60,7 @@ public: ...@@ -66,8 +60,7 @@ public:
/// @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 typename infer_handle_from_fun<F>::type spawn(F fun, Ts&&... xs) {
spawn(F fun, Ts&&... xs) {
return self_->spawn<Os>(std::move(fun), std::forward<Ts>(xs)...); return self_->spawn<Os>(std::move(fun), std::forward<Ts>(xs)...);
} }
...@@ -208,10 +201,8 @@ public: ...@@ -208,10 +201,8 @@ public:
} }
template <class... Ts, template <class... Ts,
class R = class R = typename detail::make_response_promise_helper<
typename detail::make_response_promise_helper< typename std::decay<Ts>::type...>::type>
typename std::decay<Ts>::type...
>::type>
R response(Ts&&... xs) { R response(Ts&&... xs) {
return self_->response(std::forward<Ts>(xs)...); return self_->response(std::forward<Ts>(xs)...);
} }
...@@ -230,6 +221,11 @@ public: ...@@ -230,6 +221,11 @@ public:
std::move(bhvr)); std::move(bhvr));
} }
template <class Handle, class... Ts>
auto delegate(const Handle& dest, Ts&&... xs) {
return self_->delegate(dest, std::forward<Ts>(xs)...);
}
/// Returns a pointer to the sender of the current message. /// Returns a pointer to the sender of the current message.
/// @pre `current_mailbox_element() != nullptr` /// @pre `current_mailbox_element() != nullptr`
strong_actor_ptr& current_sender() { strong_actor_ptr& current_sender() {
...@@ -244,7 +240,8 @@ public: ...@@ -244,7 +240,8 @@ public:
/// @private /// @private
actor_control_block* ctrl() const noexcept { actor_control_block* ctrl() const noexcept {
CAF_ASSERT(self_ != nullptr); CAF_ASSERT(self_ != nullptr);
return actor_control_block::from(self_);; return actor_control_block::from(self_);
;
} }
/// @private /// @private
...@@ -252,6 +249,11 @@ public: ...@@ -252,6 +249,11 @@ public:
return self_; return self_;
} }
/// @private
void reset(scheduled_actor* ptr) {
self_ = ptr;
}
operator scheduled_actor*() const noexcept { operator scheduled_actor*() const noexcept {
return self_; return self_;
} }
......
...@@ -31,7 +31,6 @@ ...@@ -31,7 +31,6 @@
#include "caf/exit_reason.hpp" #include "caf/exit_reason.hpp"
#include "caf/actor_system.hpp" #include "caf/actor_system.hpp"
#include "caf/scoped_actor.hpp" #include "caf/scoped_actor.hpp"
#include "caf/stateful_actor.hpp"
#include "caf/event_based_actor.hpp" #include "caf/event_based_actor.hpp"
#include "caf/uniform_type_info_map.hpp" #include "caf/uniform_type_info_map.hpp"
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#include "caf/scheduler/coordinator.hpp" #include "caf/scheduler/coordinator.hpp"
#include "caf/scheduler/test_coordinator.hpp" #include "caf/scheduler/test_coordinator.hpp"
#include "caf/send.hpp" #include "caf/send.hpp"
#include "caf/stateful_actor.hpp"
#include "caf/to_string.hpp" #include "caf/to_string.hpp"
namespace caf { namespace caf {
......
...@@ -16,18 +16,18 @@ ...@@ -16,18 +16,18 @@
* http://www.boost.org/LICENSE_1_0.txt. * * http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/ ******************************************************************************/
#include "caf/config.hpp"
#define CAF_SUITE stateful_actor #define CAF_SUITE stateful_actor
#include "caf/test/unit_test.hpp"
#include "caf/all.hpp" #include "caf/stateful_actor.hpp"
#include "caf/test/dsl.hpp"
#define ERROR_HANDLER [&](error& err) { CAF_FAIL(system.render(err)); } #include "caf/event_based_actor.hpp"
using namespace std;
using namespace caf; using namespace caf;
using namespace std::string_literals;
namespace { namespace {
using typed_adder_actor using typed_adder_actor
...@@ -35,7 +35,6 @@ using typed_adder_actor ...@@ -35,7 +35,6 @@ using typed_adder_actor
struct counter { struct counter {
int value = 0; int value = 0;
local_actor* self_;
}; };
behavior adder(stateful_actor<counter>* self) { behavior adder(stateful_actor<counter>* self) {
...@@ -77,36 +76,32 @@ public: ...@@ -77,36 +76,32 @@ public:
} }
}; };
struct fixture { struct fixture : test_coordinator_fixture<> {
actor_system_config cfg; fixture() {
actor_system system;
fixture() : system(cfg) {
// nop // nop
} }
template <class ActorUnderTest> template <class ActorUnderTest>
void test_adder(ActorUnderTest aut) { void test_adder(ActorUnderTest aut) {
scoped_actor self{system}; inject((add_atom, int), from(self).to(aut).with(add_atom_v, 7));
self->send(aut, add_atom_v, 7); inject((add_atom, int), from(self).to(aut).with(add_atom_v, 4));
self->send(aut, add_atom_v, 4); inject((add_atom, int), from(self).to(aut).with(add_atom_v, 9));
self->send(aut, add_atom_v, 9); inject((get_atom), from(self).to(aut).with(get_atom_v));
self->request(aut, infinite, get_atom_v) expect((int), from(aut).to(self).with(20));
.receive([](int x) { CAF_CHECK_EQUAL(x, 20); }, ERROR_HANDLER);
} }
template <class State> template <class State>
void test_name(const char* expected) { void test_name(const char* expected) {
auto aut = system.spawn([](stateful_actor<State>* self) -> behavior { auto aut = sys.spawn([](stateful_actor<State>* self) -> behavior {
return [=](get_atom) { return {
[=](get_atom) {
self->quit(); self->quit();
return self->name(); return self->name();
},
}; };
}); });
scoped_actor self{system}; inject((get_atom), from(self).to(aut).with(get_atom_v));
self->request(aut, infinite, get_atom_v) expect((std::string), from(aut).to(self).with(expected));
.receive([&](const string& str) { CAF_CHECK_EQUAL(str, expected); },
ERROR_HANDLER);
} }
}; };
...@@ -114,42 +109,108 @@ struct fixture { ...@@ -114,42 +109,108 @@ struct fixture {
CAF_TEST_FIXTURE_SCOPE(dynamic_stateful_actor_tests, fixture) CAF_TEST_FIXTURE_SCOPE(dynamic_stateful_actor_tests, fixture)
CAF_TEST(dynamic_stateful_actor) { CAF_TEST(stateful actors can be dynamically typed) {
CAF_REQUIRE(monitored + monitored == monitored); test_adder(sys.spawn(adder));
test_adder(system.spawn(adder)); test_adder(sys.spawn<typed_adder_class>());
}
CAF_TEST(typed_stateful_actor) {
test_adder(system.spawn(typed_adder));
}
CAF_TEST(dynamic_stateful_actor_class) {
test_adder(system.spawn<adder_class>());
} }
CAF_TEST(typed_stateful_actor_class) { CAF_TEST(stateful actors can be statically typed) {
test_adder(system.spawn<typed_adder_class>()); test_adder(sys.spawn(typed_adder));
test_adder(sys.spawn<adder_class>());
} }
CAF_TEST(no_name) { CAF_TEST(stateful actors without explicit name use the name of the parent) {
struct state { struct state {
// empty // empty
}; };
test_name<state>("scheduled_actor"); test_name<state>("scheduled_actor");
} }
CAF_TEST(char_name) { CAF_TEST(states with C string names override the default name) {
struct state { struct state {
const char* name = "testee"; const char* name = "testee";
}; };
test_name<state>("testee"); test_name<state>("testee");
} }
CAF_TEST(string_name) { CAF_TEST(states with STL string names override the default name) {
struct state { struct state {
string name = "testee2"; std::string name = "testee2";
}; };
test_name<state>("testee2"); test_name<state>("testee2");
} }
CAF_TEST(states can accept constructor arguments and provide a behavior) {
struct state_type {
int x;
int y;
state_type(int x, int y) : x(x), y(y) {
// nop
}
behavior make_behavior() {
return {
[=](int x, int y) {
this->x = x;
this->y = y;
},
};
}
};
using actor_type = stateful_actor<state_type>;
auto testee = sys.spawn<actor_type>(10, 20);
auto& state = deref<actor_type>(testee).state;
CAF_CHECK_EQUAL(state.x, 10);
CAF_CHECK_EQUAL(state.y, 20);
inject((int, int), to(testee).with(1, 2));
CAF_CHECK_EQUAL(state.x, 1);
CAF_CHECK_EQUAL(state.y, 2);
}
CAF_TEST(states optionally take the self pointer as first argument) {
struct state_type {
event_based_actor* self;
int x;
const char* name = "testee";
state_type(event_based_actor* self, int x) : self(self), x(x) {
// nop
}
behavior make_behavior() {
return {
[=](get_atom) { return self->name(); },
};
}
};
using actor_type = stateful_actor<state_type>;
auto testee = sys.spawn<actor_type>(10);
auto& state = deref<actor_type>(testee).state;
CAF_CHECK(state.self == &deref<actor_type>(testee));
CAF_CHECK_EQUAL(state.x, 10);
inject((get_atom), from(self).to(testee).with(get_atom_v));
expect((std::string), from(testee).to(self).with("testee"s));
}
CAF_TEST(typed actors can use typed_actor_pointer as self pointer) {
struct state_type {
using self_pointer = typed_adder_actor::pointer_view;
self_pointer self;
const char* name = "testee";
int value;
state_type(self_pointer self, int x) : self(self), value(x) {
// nop
}
auto make_behavior() {
return make_typed_behavior([=](add_atom, int x) { value += x; },
[=](get_atom) { return value; });
}
};
using actor_type = typed_adder_actor::stateful_base<state_type>;
auto testee = sys.spawn<actor_type>(10);
auto& state = deref<actor_type>(testee).state;
CAF_CHECK(state.self == &deref<actor_type>(testee));
CAF_CHECK_EQUAL(state.value, 10);
inject((add_atom, int), from(self).to(testee).with(add_atom_v, 1));
inject((get_atom), from(self).to(testee).with(get_atom_v));
expect((int), from(testee).to(self).with(11));
}
CAF_TEST_FIXTURE_SCOPE_END() CAF_TEST_FIXTURE_SCOPE_END()
...@@ -458,10 +458,11 @@ public: ...@@ -458,10 +458,11 @@ public:
} }
void with(Ts... xs) { void with(Ts... xs) {
if (src_ == nullptr)
CAF_FAIL("missing .from() in inject() statement");
if (dest_ == nullptr) if (dest_ == nullptr)
CAF_FAIL("missing .to() in inject() statement"); CAF_FAIL("missing .to() in inject() statement");
if (src_ == nullptr)
caf::anon_send(caf::actor_cast<caf::actor>(dest_), xs...);
else
caf::send_as(caf::actor_cast<caf::actor>(src_), caf::send_as(caf::actor_cast<caf::actor>(src_),
caf::actor_cast<caf::actor>(dest_), xs...); caf::actor_cast<caf::actor>(dest_), xs...);
CAF_REQUIRE(sched_.prioritize(dest_)); CAF_REQUIRE(sched_.prioritize(dest_));
......
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