Commit d19ca0d6 authored by Dominik Charousset's avatar Dominik Charousset

Enable states with non-default constructor

parent 99212ecc
...@@ -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,89 @@ ...@@ -24,88 +24,89 @@
#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 { typename Base::behavior_type make_behavior() 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. /// Evaluates to either `stateful_actor_base<State, Base> `or `Base`, depending
State& state; /// 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>;
/// @cond PRIVATE } // namespace caf::detail
void initialize() override { namespace caf {
Base::initialize();
}
/// @endcond
private: /// An event-based actor with managed state. The state is constructed with the
template <class T> /// actor, but destroyed when the actor calls `quit`. This state management
typename std::enable_if<std::is_constructible<State, T>::value>::type /// brakes cycles and allows actors to automatically release resources as soon
cr_state(T arg) { /// as possible.
new (&state_) State(arg); 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>;
template <class T> template <class... Ts>
typename std::enable_if<!std::is_constructible<State, T>::value>::type explicit stateful_actor(actor_config& cfg, Ts&&... xs) : super(cfg) {
cr_state(T) { using pointer = stateful_actor*;
new (&state_) State(); if constexpr (std::is_constructible<State, pointer, Ts&&...>::value) {
new (&state) State(this, std::forward<Ts>(xs)...);
} else {
new (&state) State(std::forward<Ts>(xs)...);
} }
static const char* unbox_str(const char* str) {
return str;
} }
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)); \
......
...@@ -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,84 @@ struct fixture { ...@@ -114,42 +109,84 @@ 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) { CAF_TEST(stateful actors can be statically typed) {
test_adder(system.spawn<adder_class>()); test_adder(sys.spawn(typed_adder));
test_adder(sys.spawn<adder_class>());
} }
CAF_TEST(typed_stateful_actor_class) { CAF_TEST(stateful actors without explicit name use the name of the parent) {
test_adder(system.spawn<typed_adder_class>());
}
CAF_TEST(no_name) {
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_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