-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
55e0fef
commit 7830268
Showing
9 changed files
with
381 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
#pragma once | ||
|
||
#include <algorithm> | ||
#include <concepts> | ||
#include <type_traits> | ||
|
||
#include <riw/notify/observer.hpp> | ||
|
||
namespace riw { | ||
template <std::copy_constructible Type> | ||
class behavior : public riw::observer<Type> { | ||
public: | ||
using value_type = riw::observer<Type>::value_type; | ||
|
||
constexpr behavior(value_type initial_value) : current_value{initial_value} {} | ||
|
||
template <class... Args> | ||
constexpr behavior(Args &&...args) : current_value{std::forward<Args>(args)...} {} | ||
|
||
behavior(const behavior &) = default; | ||
behavior(behavior &&) = default; | ||
|
||
behavior &operator=(const value_type &value) { | ||
this->current_value = value; | ||
this->notify(observe()); | ||
return *this; | ||
} | ||
behavior &operator=(value_type &&value) { | ||
this->current_value = std::move(value); | ||
this->notify(observe()); | ||
return *this; | ||
} | ||
|
||
operator value_type() const { return current_value; } | ||
value_type observe() const override { return current_value; } | ||
|
||
private: | ||
value_type current_value; | ||
}; | ||
|
||
} // namespace riw |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
#pragma once | ||
|
||
#include <concepts> | ||
|
||
namespace riw { | ||
template <class ObservableType> | ||
concept observable = | ||
std::copy_constructible<typename ObservableType::value_type> && | ||
requires(ObservableType o, typename ObservableType::subscription_type &subscription) { | ||
{ o.observe() } -> std::same_as<typename ObservableType::value_type>; | ||
o.add_subscription(subscription); | ||
o.remove_subscription(subscription); | ||
}; | ||
} // namespace riw |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#pragma once | ||
|
||
#include <concepts> | ||
|
||
namespace riw { | ||
template <class S> | ||
concept subscribable = std::copy_constructible<typename S::value_type> && | ||
requires(S s) { s.receive(std::declval<typename S::value_type>); }; | ||
} // namespace riw |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
#pragma once | ||
|
||
#include <type_traits> | ||
|
||
#include <riw/notify/concepts/observable.hpp> | ||
#include <riw/notify/concepts/subscribable.hpp> | ||
|
||
namespace riw { | ||
|
||
namespace detail { | ||
template <class F, class O> | ||
concept connectable_observable_to_function = | ||
observable<O> && std::is_invocable_v<F, typename O::value_type> && | ||
!std::is_same_v<std::invoke_result_t<F, typename O::value_type>, void>; | ||
|
||
template <observable O, connectable_observable_to_function<O> F> | ||
struct connection_observable_traits { | ||
using value_type = typename O::value_type; | ||
using invoke_result_type = std::invoke_result_t<F, value_type>; | ||
|
||
using observer_type = observer<invoke_result_type>; | ||
using subscription_type = subscription<value_type>; | ||
}; | ||
|
||
template <observable O, connectable_observable_to_function<O> F> | ||
class observable_connection : public connection_observable_traits<O, F>::observer_type, | ||
public connection_observable_traits<O, F>::subscription_type { | ||
public: | ||
using value_type = connection_observable_traits<O, F>::invoke_result_type; | ||
|
||
constexpr observable_connection(O &o, F &&f) : obs{o}, func{std::forward<F>(f)} { | ||
obs.add_subscription(*this); | ||
} | ||
|
||
~observable_connection() { obs.remove_subscription(*this); } | ||
|
||
operator value_type() const { return func(obs.observe()); } | ||
value_type observe() const override { return func(obs.observe()); } | ||
|
||
void receive(typename connection_observable_traits<O, F>::value_type v) override { | ||
connection_observable_traits<O, F>::observer_type::notify(func(v)); | ||
} | ||
|
||
private: | ||
O &obs; | ||
const F func; | ||
}; | ||
|
||
template <observable O, connectable_observable_to_function<O> F> | ||
class observable_connection_r : public connection_observable_traits<O, F>::observer_type, | ||
public connection_observable_traits<O, F>::subscription_type { | ||
public: | ||
using value_type = connection_observable_traits<O, F>::invoke_result_type; | ||
|
||
constexpr observable_connection_r(O &&o, F &&f) | ||
: obs{std::forward<O>(o)}, func{std::forward<F>(f)} { | ||
obs.add_subscription(*this); | ||
} | ||
|
||
~observable_connection_r() { obs.remove_subscription(*this); } | ||
|
||
operator value_type() const { return func(obs.observe()); } | ||
value_type observe() const override { return func(obs.observe()); } | ||
|
||
void receive(typename connection_observable_traits<O, F>::value_type v) override { | ||
connection_observable_traits<O, F>::observer_type::notify(func(v)); | ||
} | ||
|
||
private: | ||
O obs; | ||
const F func; | ||
}; | ||
|
||
template <class F, class O> | ||
concept connectable_observable_to_void_function = | ||
observable<O> && std::is_invocable_v<F, typename O::value_type> && | ||
std::is_same_v<std::invoke_result_t<F, typename O::value_type>, void>; | ||
|
||
template <observable O, connectable_observable_to_void_function<O> F> | ||
struct connection_subscription_traits { | ||
using value_type = typename O::value_type; | ||
|
||
using subscription_type = subscription<value_type>; | ||
}; | ||
|
||
template <observable O, connectable_observable_to_void_function<O> F> | ||
class subscription_connection : public connection_subscription_traits<O, F>::subscription_type { | ||
public: | ||
constexpr subscription_connection(O &o, F &&f) : obs{o}, func{std::forward<F>(f)} { | ||
obs.add_subscription(*this); | ||
} | ||
|
||
~subscription_connection() { obs.remove_subscription(*this); } | ||
|
||
void receive(typename connection_subscription_traits<O, F>::value_type v) override { func(v); } | ||
|
||
private: | ||
O &obs; | ||
const F func; | ||
}; | ||
|
||
template <observable O, connectable_observable_to_void_function<O> F> | ||
class subscription_connection_r : public connection_subscription_traits<O, F>::subscription_type { | ||
public: | ||
constexpr subscription_connection_r(O &o, F &&f) | ||
: obs{std::forward<O>(o)}, func{std::forward<F>(f)} { | ||
obs.add_subscription(*this); | ||
} | ||
|
||
~subscription_connection_r() { obs.remove_subscription(*this); } | ||
|
||
void receive(typename connection_subscription_traits<O, F>::value_type v) override { func(v); } | ||
|
||
private: | ||
O obs; | ||
const F func; | ||
}; | ||
} // namespace detail | ||
|
||
// to observable | ||
template <observable O, detail::connectable_observable_to_function<O> F> | ||
[[nodiscard]] inline auto operator|(O &o, F &&f) { | ||
return detail::observable_connection<O, F>{o, std::forward<F>(f)}; | ||
} | ||
|
||
template <observable O, detail::connectable_observable_to_function<O> F> | ||
[[nodiscard]] inline auto operator|(O &&o, F &&f) { | ||
return detail::observable_connection_r<O, F>{std::forward<O>(o), std::forward<F>(f)}; | ||
} | ||
|
||
// to subscription | ||
template <observable O, detail::connectable_observable_to_void_function<O> F> | ||
[[nodiscard]] inline auto operator|(O &o, F &&f) { | ||
return detail::subscription_connection<O, F>{o, std::forward<F>(f)}; | ||
} | ||
|
||
template <observable O, detail::connectable_observable_to_void_function<O> F> | ||
[[nodiscard]] inline auto operator|(O &&o, F &&f) { | ||
return detail::subscription_connection_r<O, F>{std::forward<O>(o), std::forward<F>(f)}; | ||
} | ||
|
||
} // namespace riw |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
#pragma once | ||
|
||
#include <vector> | ||
|
||
#include <riw/notify/concepts/observable.hpp> | ||
#include <riw/notify/subsctiption.hpp> | ||
|
||
namespace riw { | ||
template <std::copy_constructible Type> | ||
class observer { | ||
public: | ||
using value_type = Type; | ||
using subscription_type = riw::subscription<Type>; | ||
|
||
void add_subscription(subscription_type &s) { | ||
subscriptions.emplace_back(s); | ||
s.receive(observe()); | ||
} | ||
|
||
void remove_subscription(subscription_type &s) { | ||
if (auto it = std::remove_if(std::begin(subscriptions), std::end(subscriptions), | ||
[&s](const auto &ss) { return &(ss.get()) == &s; }); | ||
it != std::end(subscriptions)) { | ||
subscriptions.erase(it); | ||
} | ||
} | ||
|
||
virtual value_type observe() const = 0; | ||
|
||
virtual void notify(value_type v) { | ||
for (auto &sub : subscriptions) { | ||
sub.get().receive(v); | ||
} | ||
} | ||
|
||
std::size_t subscriptions_count() const { return subscriptions.size(); }; | ||
|
||
private: | ||
std::vector<std::reference_wrapper<subscription_type>> subscriptions; | ||
}; | ||
} // namespace riw |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
#pragma once | ||
|
||
#include <concepts> | ||
|
||
namespace riw { | ||
template <class Type> | ||
class subscription { | ||
public: | ||
using value_type = Type; | ||
|
||
virtual void receive(value_type v) = 0; | ||
}; | ||
} // namespace riw |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
cc_test( | ||
name = "notify_test", | ||
size = "small", | ||
srcs = glob(["*.cpp"]), | ||
deps = [ | ||
"//:riw_test_library", | ||
"@googletest//:gtest", | ||
"@googletest//:gtest_main", | ||
], | ||
visibility = ["//visibility:public"], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
#include <iostream> | ||
#include <string> | ||
|
||
#include <gtest/gtest.h> | ||
#include <riw/notify/behavior.hpp> | ||
#include <riw/notify/connection.hpp> | ||
|
||
TEST(Notify_BehaviorTest, Construction) { | ||
class ClassA {}; | ||
|
||
riw::behavior<int> i{10}; | ||
riw::behavior<std::string> s{"string"}; | ||
riw::behavior<ClassA> c{}; | ||
} | ||
|
||
TEST(Notify_BehaviorTest, Cast) { | ||
riw::behavior<int> i{10}; | ||
ASSERT_EQ(i, 10); | ||
|
||
riw::behavior<std::string> s{"string"}; | ||
ASSERT_STREQ(static_cast<std::string>(s).c_str(), "string"); | ||
} | ||
|
||
TEST(Notify_BehaviorTest, SubstitutionOperator) { | ||
{ | ||
riw::behavior<int> i{10}; | ||
i = 42; | ||
ASSERT_EQ(i, 42); | ||
} | ||
{ | ||
riw::behavior<std::string> s{"string"}; | ||
s = std::string{"namasute"}; | ||
|
||
ASSERT_STREQ(static_cast<std::string>(s).c_str(), "namasute"); | ||
} | ||
{ | ||
struct ClassA { | ||
int x; | ||
}; | ||
riw::behavior<ClassA> c{ClassA{.x = 42}}; | ||
c = ClassA{.x = 4242}; | ||
ASSERT_EQ(static_cast<ClassA>(c).x, 4242); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
#include <iostream> | ||
#include <string> | ||
|
||
#include <gtest/gtest.h> | ||
#include <riw/notify/behavior.hpp> | ||
#include <riw/notify/connection.hpp> | ||
|
||
using namespace std::literals::string_literals; | ||
|
||
TEST(Notify_ConnectionTest, Pipeline) { | ||
riw::behavior<int> i{10}; | ||
|
||
ASSERT_EQ(i, 10); | ||
|
||
auto o = i | [](auto i) { return i * 2; }; | ||
ASSERT_EQ(o, 20); | ||
|
||
auto oo = i | [](auto i) { return i * 2; } | [](auto i) { return i * 2; }; | ||
ASSERT_EQ(oo, 40); | ||
|
||
auto ooo = oo | [](auto i) { return i * 2; }; | ||
ASSERT_EQ(ooo, 80); | ||
} | ||
|
||
TEST(Notify_ConnectionTest, Transform) { | ||
riw::behavior<int> i{10}; | ||
|
||
ASSERT_EQ(i, 10); | ||
|
||
auto o = i | [](auto i) { return std::to_string(i); } | [](auto s) { return s + "nyan"s; }; | ||
|
||
ASSERT_EQ(static_cast<std::string>(o), "10nyan"s); | ||
} | ||
|
||
TEST(Notify_ConnectionTest, Effect) { | ||
riw::behavior<int> i{10}; | ||
|
||
ASSERT_EQ(i, 10); | ||
|
||
auto o = i | [](auto i) { | ||
std::cout << "value: " << i; | ||
return i * 2; | ||
}; | ||
|
||
testing::internal::CaptureStdout(); | ||
i = 42; | ||
std::string log = testing::internal::GetCapturedStdout(); | ||
|
||
ASSERT_EQ(i, 42); | ||
ASSERT_EQ(static_cast<std::string>(log), "value: 42"s); | ||
} | ||
|
||
TEST(Notify_ConnectionTest, Notify) { | ||
riw::behavior<int> i{42}; | ||
|
||
ASSERT_EQ(i, 42); | ||
auto o = i | [](auto i) { return std::to_string(i); }; | ||
|
||
testing::internal::CaptureStdout(); | ||
|
||
auto s = o | [](auto s) { std::cout << "string: " << s << std::endl; }; | ||
|
||
std::string log = testing::internal::GetCapturedStdout(); | ||
|
||
ASSERT_EQ(static_cast<std::string>(log), "string: 42\n"s); | ||
} |