Commit e828088f authored by Dominik Charousset's avatar Dominik Charousset

Use type_erased_tuple for matching

parent 4a9a47e1
......@@ -97,14 +97,20 @@ public:
}
/// Runs this handler and returns its (optional) result.
inline optional<message> operator()(message& x) {
return impl_ ? impl_->invoke(x) : none;
inline optional<message> operator()(message& xs) {
return impl_ ? impl_->invoke(xs) : none;
}
/// Runs this handler with callback.
inline match_case::result operator()(detail::invoke_result_visitor& f,
message& x) {
return impl_ ? impl_->invoke(f, x) : match_case::no_match;
type_erased_tuple& xs) {
return impl_ ? impl_->invoke(f, xs) : match_case::no_match;
}
/// Runs this handler with callback.
inline match_case::result operator()(detail::invoke_result_visitor& f,
message& xs) {
return impl_ ? impl_->invoke(f, xs) : match_case::no_match;
}
/// Checks whether this behavior is not empty.
......
......@@ -68,16 +68,17 @@ public:
behavior_impl(duration tout = duration{});
virtual match_case::result invoke(detail::invoke_result_visitor& f, message&);
virtual match_case::result invoke_empty(detail::invoke_result_visitor& f);
inline match_case::result invoke(detail::invoke_result_visitor& f,
message&& arg) {
message tmp(std::move(arg));
return invoke(f, tmp);
}
virtual match_case::result invoke(detail::invoke_result_visitor& f,
type_erased_tuple& xs);
match_case::result invoke(detail::invoke_result_visitor& f, message& xs);
optional<message> invoke(message&);
optional<message> invoke(type_erased_tuple&);
virtual void handle_timeout();
inline const duration& timeout() const {
......
......@@ -38,14 +38,12 @@ struct meta_element {
atom_value v;
uint16_t typenr;
const std::type_info* type;
bool (*fun)(const meta_element&, const type_erased_tuple*, size_t, void**);
bool (*fun)(const meta_element&, const type_erased_tuple&, size_t);
};
bool match_element(const meta_element&, const type_erased_tuple*,
size_t, void**);
bool match_element(const meta_element&, const type_erased_tuple&, size_t);
bool match_atom_constant(const meta_element&, const type_erased_tuple*,
size_t, void**);
bool match_atom_constant(const meta_element&, const type_erased_tuple&, size_t);
template <class T, uint16_t TN = type_nr<T>::value>
struct meta_element_factory {
......@@ -80,8 +78,8 @@ struct meta_elements<type_list<Ts...>> {
}
};
bool try_match(const type_erased_tuple* xs, const meta_element* pattern_begin,
size_t pattern_size, void** out);
bool try_match(const type_erased_tuple& xs, const meta_element* pattern_begin,
size_t pattern_size);
} // namespace detail
} // namespace caf
......
......@@ -705,6 +705,16 @@ template <class F, class T>
using is_handler_for_ef =
typename std::enable_if<is_handler_for<F, T>::value>::type;
template <class T>
struct strip_reference_wrapper {
using type = T;
};
template <class T>
struct strip_reference_wrapper<std::reference_wrapper<T>> {
using type = T;
};
} // namespace detail
} // namespace caf
......
......@@ -27,6 +27,7 @@ namespace caf {
// -- 1 param templates --------------------------------------------------------
template <class> class param;
template <class> class optional;
template <class> class intrusive_ptr;
template <class> class behavior_type_of;
......
......@@ -20,6 +20,7 @@
#ifndef CAF_MATCH_CASE_HPP
#define CAF_MATCH_CASE_HPP
#include <tuple>
#include <type_traits>
#include "caf/none.hpp"
......@@ -55,7 +56,9 @@ public:
virtual ~match_case();
virtual result invoke(detail::invoke_result_visitor&, type_erased_tuple*) = 0;
/// Tries to invoke this match case with the contents of `xs`.
virtual result invoke(detail::invoke_result_visitor& rv,
type_erased_tuple& xs) = 0;
inline uint32_t type_token() const {
return token_;
......@@ -65,38 +68,6 @@ private:
uint32_t token_;
};
template <class T>
T&& unopt(T&& v,
typename std::enable_if<std::is_rvalue_reference<T&&>::value>::type* = 0) {
return std::move(v);
}
template <class T>
T& unopt(T& v) {
return v;
}
template <class T>
T& unopt(optional<T>& v) {
return *v;
}
struct has_none {
inline bool operator()() const {
return false;
}
template <class T, class... Ts>
bool operator()(const T&, const Ts&... xs) const {
return (*this)(xs...);
}
template <class T, class... Ts>
bool operator()(const optional<T>& x, const Ts&... xs) const {
return ! x || (*this)(xs...);
}
};
template <bool IsVoid, class F>
class lfinvoker {
public:
......@@ -106,7 +77,7 @@ public:
template <class... Ts>
typename detail::get_callable_trait<F>::result_type operator()(Ts&&... xs) {
return fun_(unopt(std::forward<Ts>(xs))...);
return fun_(std::forward<Ts>(xs)...);
}
private:
......@@ -122,7 +93,7 @@ public:
template <class... Ts>
unit_t operator()(Ts&&... xs) {
fun_(unopt(std::forward<Ts>(xs))...);
fun_(std::forward<Ts>(xs)...);
return unit;
}
......@@ -166,12 +137,15 @@ public:
"down_msg not allowed in message handlers, "
"did you mean to use set_down_handler()?");
using intermediate_tuple =
using decayed_arg_types =
typename detail::tl_map<
arg_types,
std::decay
>::type;
using intermediate_pseudo_tuple =
typename detail::tl_apply<
typename detail::tl_map<
arg_types,
std::decay
>::type,
decayed_arg_types,
detail::pseudo_tuple
>::type;
......@@ -185,27 +159,25 @@ public:
}
match_case::result invoke(detail::invoke_result_visitor& f,
type_erased_tuple* xs) override {
intermediate_tuple it{xs ? xs->shared() : false};
type_erased_tuple& xs) override {
detail::meta_elements<pattern> ms;
message tmp;
// check if try_match() reports success
if (! detail::try_match(xs, ms.arr.data(), ms.arr.size(), it.data))
if (! detail::try_match(xs, ms.arr.data(), ms.arr.size()))
return match_case::no_match;
// detach msg before invoking fun_ if needed
if (is_manipulator && it.shared_access) {
tmp = message::from(xs);
tmp.force_unshare();
it.shared_access = false;
// update pointers in our intermediate tuple
for (size_t i = 0; i < tmp.size(); ++i) {
// msg is guaranteed to be detached, hence we don't need to
// check this condition over and over again via get_mutable
it[i] = const_cast<void*>(tmp.at(i));
}
}
typename detail::il_indices<decayed_arg_types>::type indices;
lfinvoker<std::is_same<result_type, void>::value, F> fun{fun_};
auto fun_res = apply_args(fun, detail::get_indices(it), it);
message tmp;
intermediate_pseudo_tuple tup{xs.shared()};
if (is_manipulator && tup.shared_access) {
tmp = message::copy_from(&xs);
tup.shared_access = false;
for (size_t i = 0; i < tmp.size(); ++i)
tup[i] = const_cast<void*>(tmp.at(i));
} else {
for (size_t i = 0; i < xs.size(); ++i)
tup[i] = const_cast<void*>(xs.get(i));
}
auto fun_res = apply_args(fun, indices, tup);
return f.visit(fun_res) ? match_case::match : match_case::skip;
}
......
......@@ -272,13 +272,18 @@ public:
return size() == sizeof...(Ts) && match_elements_impl(p0, tlist);
}
inline std::pair<uint16_t, const std::type_info*> type(size_t pos) const {
return vals_->type(pos);
}
message& operator+=(const message& x);
/// Creates a message object from `ptr`.
static message from(const type_erased_tuple* ptr);
/// @cond PRIVATE
static message copy_from(const type_erased_tuple* ptr);
/// @cond PRIVATE
using raw_ptr = detail::message_data*;
using data_ptr = detail::message_data::cow_ptr;
......
......@@ -67,6 +67,16 @@ public:
return emplace(make_type_erased_value<type>(std::forward<T>(x)));
}
inline message_builder& append_all() {
return *this;
}
template <class T, class... Ts>
message_builder& append_all(T&& x, Ts&&... xs) {
append(std::forward<T>(x));
return append_all(std::forward<Ts>(xs)...);
}
/// Converts the buffer to an actual message object without
/// invalidating this message builder (nor clearing it).
message to_message() const;
......
......@@ -93,6 +93,11 @@ public:
return (impl_) ? impl_->invoke(arg) : none;
}
/// Runs this handler and returns its (optional) result.
inline optional<message> operator()(type_erased_tuple& xs) {
return impl_ ? impl_->invoke(xs) : none;
}
/// Returns a new handler that concatenates this handler
/// with a new handler from `xs...`.
template <class... Ts>
......@@ -110,9 +115,8 @@ public:
if (! tmp) {
return *this;
}
if (impl_) {
if (impl_)
return impl_->or_else(tmp.as_behavior_impl());
}
return tmp.as_behavior_impl();
}
......
......@@ -215,7 +215,7 @@ private:
// -- data members -----------------------------------------------------------
std::tuple<type_erased_value_impl<std::reference_wrapper<Ts>>...> xs_;
type_erased_value* ptrs_[sizeof...(Ts)];
type_erased_value* ptrs_[sizeof...(Ts) == 0 ? 1 : sizeof...(Ts)];
};
template <class... Ts>
......
......@@ -112,6 +112,10 @@ inline std::string to_string(const type_erased_value& x) {
template <class T>
class type_erased_value_impl : public type_erased_value {
public:
// -- member types -----------------------------------------------------------
using value_type = typename detail::strip_reference_wrapper<T>::type;
// -- constructors, destructors, and assignment operators --------------------
template <class... Ts>
......@@ -153,9 +157,18 @@ public:
// -- overridden observers ---------------------------------------------------
static rtti_pair type(std::integral_constant<uint16_t, 0>) {
return {0, &typeid(value_type)};
}
template <uint16_t V>
static rtti_pair type(std::integral_constant<uint16_t, V>) {
return {V, nullptr};
}
rtti_pair type() const override {
auto nr = caf::type_nr<T>::value;
return {nr, &typeid(T)};
std::integral_constant<uint16_t, caf::type_nr<value_type>::value> token;
return type(token);
}
const void* get() const override {
......@@ -198,18 +211,6 @@ private:
return &x.get();
}
// -- typeid utility ---------------------------------------------------------
template <class U>
static inline const std::type_info* typeid_of(U&) {
return &typeid(U);
}
template <class U>
static inline const std::type_info* typeid_of(std::reference_wrapper<U>&) {
return &typeid(U);
}
// -- array copying utility --------------------------------------------------
template <class U, size_t Len>
......
......@@ -28,9 +28,10 @@ namespace {
class combinator final : public behavior_impl {
public:
match_case::result invoke(detail::invoke_result_visitor& f, message& arg) override {
auto x = first->invoke(f, arg);
return x == match_case::no_match ? second->invoke(f, arg) : x;
match_case::result invoke(detail::invoke_result_visitor& f,
type_erased_tuple& xs) override {
auto x = first->invoke(f, xs);
return x == match_case::no_match ? second->invoke(f, xs) : x;
}
void handle_timeout() override {
......@@ -89,12 +90,18 @@ behavior_impl::behavior_impl(duration tout)
// nop
}
match_case::result
behavior_impl::invoke_empty(detail::invoke_result_visitor& f) {
auto xs = make_type_erased_tuple_view();
return invoke(f, xs);
}
match_case::result behavior_impl::invoke(detail::invoke_result_visitor& f,
message& msg) {
auto msg_token = msg.type_token();
type_erased_tuple& xs) {
auto msg_token = xs.type_token();
for (auto i = begin_; i != end_; ++i)
if (i->type_token == msg_token)
switch (i->ptr->invoke(f, msg.cvals().get())) {
switch (i->ptr->invoke(f, xs)) {
case match_case::no_match:
break;
case match_case::match:
......@@ -105,12 +112,32 @@ match_case::result behavior_impl::invoke(detail::invoke_result_visitor& f,
return match_case::no_match;
}
optional<message> behavior_impl::invoke(message& x) {
optional<message> behavior_impl::invoke(message& xs) {
maybe_message_visitor f;
invoke(f, x);
// the following const-cast is safe, because invoke() is aware of
// copy-on-write and does not modify x if it's shared
if (! xs.empty())
invoke(f, *const_cast<message_data*>(xs.cvals().get()));
else
invoke_empty(f);
return std::move(f.value);
}
optional<message> behavior_impl::invoke(type_erased_tuple& xs) {
maybe_message_visitor f;
invoke(f, xs);
return std::move(f.value);
}
match_case::result behavior_impl::invoke(detail::invoke_result_visitor& f,
message& xs) {
// the following const-cast is safe, because invoke() is aware of
// copy-on-write and does not modify x if it's shared
if (! xs.empty())
return invoke(f, *const_cast<message_data*>(xs.cvals().get()));
return invoke_empty(f);
}
void behavior_impl::handle_timeout() {
// nop
}
......
......@@ -463,7 +463,7 @@ void local_actor::handle_response(mailbox_element_ptr& ptr,
});
local_actor_invoke_result_visitor visitor{this};
auto invoke_error = [&](error err) {
auto tmp = make_message(err);
auto tmp = make_type_erased_tuple_view(err);
if (ref_fun(visitor, tmp) == match_case::no_match) {
CAF_LOG_WARNING("multiplexed response failure occured:"
<< CAF_ARG(id()));
......
......@@ -79,6 +79,13 @@ message message::from(const type_erased_tuple* ptr) {
return mb.move_to_message();
}
message message::copy_from(const type_erased_tuple* ptr) {
message_builder mb;
for (size_t i = 0; i < ptr->size(); ++i)
mb.emplace(ptr->copy(i));
return mb.move_to_message();
}
bool message::match_element(size_t pos, uint16_t typenr,
const std::type_info* rtti) const {
return vals_->matches(pos, typenr, rtti);
......
......@@ -24,43 +24,30 @@ namespace detail {
using pattern_iterator = const meta_element*;
bool match_element(const meta_element& me, const type_erased_tuple* xs,
size_t pos, void** storage) {
bool match_element(const meta_element& me, const type_erased_tuple& xs,
size_t pos) {
CAF_ASSERT(me.typenr != 0 || me.type != nullptr);
if (! xs->matches(pos, me.typenr, me.type))
return false;
*storage = const_cast<void*>(xs->get(pos));
return true;
return xs.matches(pos, me.typenr, me.type);
}
bool match_atom_constant(const meta_element& me, const type_erased_tuple* xs,
size_t pos, void** storage) {
bool match_atom_constant(const meta_element& me, const type_erased_tuple& xs,
size_t pos) {
CAF_ASSERT(me.typenr == type_nr<atom_value>::value);
if (! xs->matches(pos, type_nr<atom_value>::value, nullptr))
if (! xs.matches(pos, type_nr<atom_value>::value, nullptr))
return false;
auto ptr = xs->get(pos);
auto ptr = xs.get(pos);
if (me.v != *reinterpret_cast<const atom_value*>(ptr))
return false;
// This assignment casts `atom_value` to `atom_constant<V>*`.
// This type violation could theoretically cause undefined behavior.
// However, `uti` does have an address that is guaranteed to be valid
// throughout the runtime of the program and the atom constant
// does not have any members. Hence, this is nonetheless safe since
// we are never actually going to dereference the pointer.
*storage = const_cast<void*>(ptr);
return true;
}
bool try_match(const type_erased_tuple* xs,
pattern_iterator iter, size_t ps, void** out) {
CAF_ASSERT(out != nullptr);
if (! xs)
return ps == 0;
if (xs->size() != ps)
bool try_match(const type_erased_tuple& xs,
pattern_iterator iter, size_t ps) {
if (xs.size() != ps)
return false;
for (size_t i = 0; i < ps; ++i, ++iter, ++out)
for (size_t i = 0; i < ps; ++i, ++iter)
// inspect current element
if (! iter->fun(*iter, xs, i, out))
if (! iter->fun(*iter, xs, i))
// type mismatch
return false;
return true;
......
......@@ -178,7 +178,7 @@ public:
}
result<counting_string> operator()(get_atom, param<counting_string> key) override {
auto i = values_.find(key);
auto i = values_.find(key.get());
if (i == values_.end())
return "";
return i->second;
......@@ -186,7 +186,7 @@ public:
result<void> operator()(put_atom, param<counting_string> key,
param<counting_string> value) override {
if (values_.count(key) != 0)
if (values_.count(key.get()) != 0)
return unit;
values_.emplace(key.move(), value.move());
return unit;
......
......@@ -33,83 +33,94 @@ using namespace std;
using hi_atom = atom_constant<atom("hi")>;
using ho_atom = atom_constant<atom("ho")>;
function<optional<string>(const string&)> starts_with(const string& s) {
return [=](const string& str) -> optional<string> {
if (str.size() > s.size() && str.compare(0, s.size(), s) == 0)
return str.substr(s.size());
return none;
};
}
namespace {
optional<int> toint(const string& str) {
char* endptr = nullptr;
int result = static_cast<int>(strtol(str.c_str(), &endptr, 10));
if (endptr != nullptr && *endptr == '\0') {
return result;
}
return none;
}
using rtti_pair = std::pair<uint16_t, const std::type_info*>;
namespace {
std::string to_string(const rtti_pair& x) {
if (x.second)
return deep_to_string_as_tuple(x.first, x.second->name());
return deep_to_string_as_tuple(x.first, "<null>");
}
bool s_invoked[] = {false, false, false, false};
struct fixture {
using array_type = std::array<bool, 4>;
} // namespace <anonymous>
fixture() {
}
void reset() {
fill(begin(s_invoked), end(s_invoked), false);
}
void reset() {
for (auto& x : invoked)
x = false;
}
void fill_mb(message_builder&) {
// end of recursion
}
template <class... Ts>
ptrdiff_t invoke(message_handler expr, Ts... xs) {
auto msg1 = make_message(xs...);
auto msg2 = message_builder{}.append_all(xs...).move_to_message();
auto msg3 = make_type_erased_tuple_view(xs...);
CAF_CHECK_EQUAL(to_string(msg1), to_string(msg2));
CAF_CHECK_EQUAL(to_string(msg1), to_string(msg3));
CAF_CHECK_EQUAL(msg1.type_token(), msg2.type_token());
CAF_CHECK_EQUAL(msg1.type_token(), msg3.type_token());
std::vector<std::string> msg1_types;
std::vector<std::string> msg2_types;
std::vector<std::string> msg3_types;
for (size_t i = 0; i < msg1.size(); ++i) {
msg1_types.push_back(to_string(msg1.type(i)));
msg2_types.push_back(to_string(msg2.type(i)));
msg3_types.push_back(to_string(msg3.type(i)));
}
CAF_CHECK_EQUAL(msg1_types, msg2_types);
CAF_CHECK_EQUAL(msg1_types, msg3_types);
set<ptrdiff_t> results;
process(results, expr, msg1, msg2, msg3);
if (results.size() > 1) {
CAF_ERROR("different results reported: " << deep_to_string(results));
return -2;
}
return *results.begin();
}
template <class T, class... Ts>
void fill_mb(message_builder& mb, const T& x, const Ts&... xs) {
fill_mb(mb.append(x), xs...);
}
void process(std::set<ptrdiff_t>&, message_handler&) {
// end of recursion
}
template <class... Ts>
ptrdiff_t invoked(message_handler expr, const Ts&... xs) {
vector<message> msgs;
msgs.push_back(make_message(xs...));
message_builder mb;
fill_mb(mb, xs...);
msgs.push_back(mb.to_message());
set<ptrdiff_t> results;
for (auto& msg : msgs) {
expr(msg);
auto first = begin(s_invoked);
auto last = end(s_invoked);
auto i = find(begin(s_invoked), last, true);
results.insert(i != last && count(i, last, true) == 1 ? distance(first, i)
: -1);
template <class T, class... Ts>
void process(std::set<ptrdiff_t>& results, message_handler& expr,
T& x, Ts&... xs) {
expr(x);
results.insert(invoked_res());
reset();
process(results, expr, xs...);
}
if (results.size() > 1) {
CAF_ERROR("make_message() yielded a different result than "
"message_builder(...).to_message()");
return -2;
ptrdiff_t invoked_res() {
auto first = begin(invoked);
auto last = end(invoked);
auto i = find(first, last, true);
return i != last && count(i, last, true) == 1 ? distance(first, i) : -1;
}
return *results.begin();
}
function<void()> f(int idx) {
return [=] {
s_invoked[idx] = true;
};
}
array_type invoked;
};
} // namespace <anonymous>
CAF_TEST_FIXTURE_SCOPE(atom_constants_tests, fixture)
CAF_TEST(atom_constants) {
message_handler expr{
[](hi_atom) {
s_invoked[0] = true;
[&](hi_atom) {
invoked[0] = true;
},
[](ho_atom) {
s_invoked[1] = true;
[&](ho_atom) {
invoked[1] = true;
}
};
CAF_CHECK_EQUAL(invoked(expr, ok_atom::value), -1);
CAF_CHECK_EQUAL(invoked(expr, hi_atom::value), 0);
CAF_CHECK_EQUAL(invoked(expr, ho_atom::value), 1);
CAF_CHECK_EQUAL(invoke(expr, atom_value{ok_atom::value}), -1);
CAF_CHECK_EQUAL(invoke(expr, atom_value{hi_atom::value}), 0);
CAF_CHECK_EQUAL(invoke(expr, atom_value{ho_atom::value}), 1);
}
CAF_TEST_FIXTURE_SCOPE_END()
......@@ -112,9 +112,9 @@ CAF_TEST(message_lifetime_in_scoped_actor) {
CAF_CHECK_EQUAL(msg.cvals()->get_reference_count(), 1u);
msg = make_message(42);
self->send(self, msg);
CAF_CHECK_EQUAL(msg.cvals()->get_reference_count(), 2u);
self->receive(
[&](int& value) {
CAF_CHECK_EQUAL(msg.cvals()->get_reference_count(), 2u);
CAF_CHECK_NOT_EQUAL(&value, msg.at(0));
value = 10;
}
......
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