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).
- Our manual now uses `reStructuredText` instead of `LaTeX`. We hope this makes
extending the manual easier and lowers the barrier to entry for new
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
......
......@@ -16,7 +16,6 @@
#include "caf/function_view.hpp"
#include "caf/policy/select_all.hpp"
#include "caf/scoped_actor.hpp"
#include "caf/stateful_actor.hpp"
#include "caf/typed_actor.hpp"
#include "caf/typed_event_based_actor.hpp"
......
......@@ -33,7 +33,9 @@ public:
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
}
......
......@@ -437,6 +437,7 @@ public:
CAF_HAS_MEMBER_TRAIT(size);
CAF_HAS_MEMBER_TRAIT(data);
CAF_HAS_MEMBER_TRAIT(make_behavior);
/// Checks whether F is convertible to either `std::function<void (T&)>`
/// or `std::function<void (const T&)>`.
......
......@@ -21,7 +21,6 @@
#include "caf/abstract_composable_behavior.hpp"
#include "caf/actor.hpp"
#include "caf/actor_addr.hpp"
#include "caf/stateful_actor.hpp"
#include "caf/typed_behavior.hpp"
namespace caf {
......
......@@ -24,88 +24,87 @@
#include "caf/fwd.hpp"
#include "caf/sec.hpp"
#include "caf/logger.hpp"
#include "caf/detail/type_traits.hpp"
namespace caf {
namespace caf::detail {
/// An event-based actor with managed state. The state is constructed
/// before `make_behavior` will get called and destroyed after the
/// actor called `quit`. This state management brakes cycles and
/// allows actors to automatically release resources as soon
/// as possible.
template <class State, class Base /* = event_based_actor (see fwd.hpp) */>
class stateful_actor : public Base {
/// Conditional base type for `stateful_actor` that overrides `make_behavior` if
/// `State::make_behavior()` exists.
template <class State, class Base>
class stateful_actor_base : public Base {
public:
template <class... Ts>
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;
using Base::Base;
/// @cond PRIVATE
typename Base::behavior_type make_behavior() override;
};
void initialize() override {
Base::initialize();
}
/// Evaluates to either `stateful_actor_base<State, Base> `or `Base`, depending
/// 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:
template <class T>
typename std::enable_if<std::is_constructible<State, T>::value>::type
cr_state(T arg) {
new (&state_) State(arg);
}
namespace caf {
template <class T>
typename std::enable_if<!std::is_constructible<State, T>::value>::type
cr_state(T) {
new (&state_) State();
}
/// An event-based actor with managed state. The state is constructed with the
/// actor, but destroyed when the actor calls `quit`. This state management
/// brakes cycles and allows actors to automatically release resources as soon
/// 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) {
return str;
template <class... Ts>
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>
static const char* unbox_str(const U& str) {
return str.c_str();
~stateful_actor() override {
// nop
}
template <class U>
typename std::enable_if<detail::has_name<U>::value, const char*>::type
get_name(const U& st) const {
return unbox_str(st.name);
/// @copydoc local_actor::on_exit
/// @note when overriding this member function, make sure to call
/// `super::on_exit()` in order to clean up the state.
void on_exit() override {
state.~State();
}
template <class U>
typename std::enable_if<!detail::has_name<U>::value, const char*>::type
get_name(const U&) const {
const char* name() const override {
if constexpr (detail::has_name<State>::value) {
if constexpr (std::is_convertible<decltype(state.name),
const char*>::value)
return state.name;
else
return state.name.c_str();
} else {
return Base::name();
}
}
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::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 @@
#define CAF_MSG_TYPE_ADD_ATOM(name) \
struct name {}; \
[[maybe_unused]] constexpr bool operator==(name, name) { \
return true; \
} \
[[maybe_unused]] constexpr bool operator!=(name, name) { \
return false; \
} \
template <class Inspector> \
auto inspect(Inspector& f, name&) { \
return f(meta::type_name("caf::" #name)); \
......
......@@ -19,6 +19,7 @@
#pragma once
#include "caf/detail/type_list.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/typed_actor_view.hpp"
namespace caf {
......@@ -33,22 +34,54 @@ public:
// nop
}
template <class Supertype>
explicit typed_actor_pointer(Supertype* selfptr) : view_(selfptr) {
using namespace caf::detail;
static_assert(
tl_subset_of<type_list<Sigs...>, typename Supertype::signatures>::value,
"cannot create a pointer view to an unrelated actor type");
template <class Supertype,
class = detail::enable_if_t< //
detail::tl_subset_of<detail::type_list<Sigs...>,
typename Supertype::signatures>::value>>
typed_actor_pointer(Supertype* selfptr) : view_(selfptr) {
// 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
}
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;
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->() {
return &view_;
}
......@@ -75,16 +108,6 @@ public:
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:
typed_actor_view<Sigs...> view_;
};
......
......@@ -31,30 +31,24 @@ namespace caf {
/// Decorates a pointer to a @ref scheduled_actor with a statically typed actor
/// interface.
template <class... Sigs>
class typed_actor_view : public extend<typed_actor_view_base,
typed_actor_view<Sigs...>>::template
with<mixin::sender, mixin::requester> {
class typed_actor_view
: public extend<typed_actor_view_base, typed_actor_view<Sigs...>>::
template with<mixin::sender, mixin::requester> {
public:
/// Stores the template parameter pack.
using signatures = detail::type_list<Sigs...>;
using pointer = scheduled_actor*;
typed_actor_view(scheduled_actor* ptr) : self_(ptr) {
explicit typed_actor_view(scheduled_actor* ptr) : self_(ptr) {
// nop
}
typed_actor_view& operator=(scheduled_actor* ptr) {
self_ = ptr;
return *this;
}
// -- spawn functions --------------------------------------------------------
/// @copydoc local_actor::spawn
template <class T, spawn_options Os = no_spawn_options, class... Ts>
typename infer_handle_from_class<T>::type
spawn(Ts&&... xs) {
typename infer_handle_from_class<T>::type spawn(Ts&&... xs) {
return self_->spawn<T, Os>(std::forward<Ts>(xs)...);
}
......@@ -66,8 +60,7 @@ public:
/// @copydoc local_actor::spawn
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) {
return self_->spawn<Os>(std::move(fun), std::forward<Ts>(xs)...);
}
......@@ -208,10 +201,8 @@ public:
}
template <class... Ts,
class R =
typename detail::make_response_promise_helper<
typename std::decay<Ts>::type...
>::type>
class R = typename detail::make_response_promise_helper<
typename std::decay<Ts>::type...>::type>
R response(Ts&&... xs) {
return self_->response(std::forward<Ts>(xs)...);
}
......@@ -230,6 +221,11 @@ public:
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.
/// @pre `current_mailbox_element() != nullptr`
strong_actor_ptr& current_sender() {
......@@ -244,7 +240,8 @@ public:
/// @private
actor_control_block* ctrl() const noexcept {
CAF_ASSERT(self_ != nullptr);
return actor_control_block::from(self_);;
return actor_control_block::from(self_);
;
}
/// @private
......@@ -252,6 +249,11 @@ public:
return self_;
}
/// @private
void reset(scheduled_actor* ptr) {
self_ = ptr;
}
operator scheduled_actor*() const noexcept {
return self_;
}
......
......@@ -31,7 +31,6 @@
#include "caf/exit_reason.hpp"
#include "caf/actor_system.hpp"
#include "caf/scoped_actor.hpp"
#include "caf/stateful_actor.hpp"
#include "caf/event_based_actor.hpp"
#include "caf/uniform_type_info_map.hpp"
......
......@@ -32,6 +32,7 @@
#include "caf/scheduler/coordinator.hpp"
#include "caf/scheduler/test_coordinator.hpp"
#include "caf/send.hpp"
#include "caf/stateful_actor.hpp"
#include "caf/to_string.hpp"
namespace caf {
......
......@@ -16,18 +16,18 @@
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/config.hpp"
#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 std::string_literals;
namespace {
using typed_adder_actor
......@@ -35,7 +35,6 @@ using typed_adder_actor
struct counter {
int value = 0;
local_actor* self_;
};
behavior adder(stateful_actor<counter>* self) {
......@@ -77,36 +76,32 @@ public:
}
};
struct fixture {
actor_system_config cfg;
actor_system system;
fixture() : system(cfg) {
struct fixture : test_coordinator_fixture<> {
fixture() {
// nop
}
template <class ActorUnderTest>
void test_adder(ActorUnderTest aut) {
scoped_actor self{system};
self->send(aut, add_atom_v, 7);
self->send(aut, add_atom_v, 4);
self->send(aut, add_atom_v, 9);
self->request(aut, infinite, get_atom_v)
.receive([](int x) { CAF_CHECK_EQUAL(x, 20); }, ERROR_HANDLER);
inject((add_atom, int), from(self).to(aut).with(add_atom_v, 7));
inject((add_atom, int), from(self).to(aut).with(add_atom_v, 4));
inject((add_atom, int), from(self).to(aut).with(add_atom_v, 9));
inject((get_atom), from(self).to(aut).with(get_atom_v));
expect((int), from(aut).to(self).with(20));
}
template <class State>
void test_name(const char* expected) {
auto aut = system.spawn([](stateful_actor<State>* self) -> behavior {
return [=](get_atom) {
auto aut = sys.spawn([](stateful_actor<State>* self) -> behavior {
return {
[=](get_atom) {
self->quit();
return self->name();
},
};
});
scoped_actor self{system};
self->request(aut, infinite, get_atom_v)
.receive([&](const string& str) { CAF_CHECK_EQUAL(str, expected); },
ERROR_HANDLER);
inject((get_atom), from(self).to(aut).with(get_atom_v));
expect((std::string), from(aut).to(self).with(expected));
}
};
......@@ -114,42 +109,108 @@ struct fixture {
CAF_TEST_FIXTURE_SCOPE(dynamic_stateful_actor_tests, fixture)
CAF_TEST(dynamic_stateful_actor) {
CAF_REQUIRE(monitored + monitored == monitored);
test_adder(system.spawn(adder));
}
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(stateful actors can be dynamically typed) {
test_adder(sys.spawn(adder));
test_adder(sys.spawn<typed_adder_class>());
}
CAF_TEST(typed_stateful_actor_class) {
test_adder(system.spawn<typed_adder_class>());
CAF_TEST(stateful actors can be statically typed) {
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 {
// empty
};
test_name<state>("scheduled_actor");
}
CAF_TEST(char_name) {
CAF_TEST(states with C string names override the default name) {
struct state {
const char* name = "testee";
};
test_name<state>("testee");
}
CAF_TEST(string_name) {
CAF_TEST(states with STL string names override the default name) {
struct state {
string name = "testee2";
std::string name = "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()
......@@ -458,10 +458,11 @@ public:
}
void with(Ts... xs) {
if (src_ == nullptr)
CAF_FAIL("missing .from() in inject() statement");
if (dest_ == nullptr)
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::actor_cast<caf::actor>(dest_), xs...);
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