From 9b304ead005df08b49f1bb653f5f477b5a7b9668 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Fri, 24 Feb 2023 15:01:53 +0300 Subject: [PATCH] Add reactive expressions library --- libs/react/CMakeLists.txt | 6 ++ libs/react/include/psemek/react/map.hpp | 46 +++++++++++ libs/react/include/psemek/react/source.hpp | 42 ++++++++++ libs/react/include/psemek/react/value.hpp | 90 ++++++++++++++++++++++ 4 files changed, 184 insertions(+) create mode 100644 libs/react/CMakeLists.txt create mode 100644 libs/react/include/psemek/react/map.hpp create mode 100644 libs/react/include/psemek/react/source.hpp create mode 100644 libs/react/include/psemek/react/value.hpp diff --git a/libs/react/CMakeLists.txt b/libs/react/CMakeLists.txt new file mode 100644 index 00000000..4f9915cb --- /dev/null +++ b/libs/react/CMakeLists.txt @@ -0,0 +1,6 @@ +file(GLOB_RECURSE PSEMEK_REACT_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "include/*.hpp") +file(GLOB_RECURSE PSEMEK_REACT_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "source/*.cpp") + +psemek_add_library(psemek-react ${PSEMEK_REACT_HEADERS} ${PSEMEK_REACT_SOURCES}) +target_include_directories(psemek-react PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(psemek-react PUBLIC psemek-util) diff --git a/libs/react/include/psemek/react/map.hpp b/libs/react/include/psemek/react/map.hpp new file mode 100644 index 00000000..dc997acd --- /dev/null +++ b/libs/react/include/psemek/react/map.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +namespace psemek::react +{ + + template + auto map(F func, value ... args) + { + using R = decltype(func(std::declval()...)); + + auto node = std::make_shared>(); + auto weak_node = std::weak_ptr{node}; + + auto internal_subscriber = [weak_node]{ + if (auto node = weak_node.lock()) + { + node->cached_value = std::nullopt; + node->internal_signal(); + } + }; + + auto external_subscriber = [weak_node](auto const &){ + if (auto node = weak_node.lock()) + { + if (!node->cached_value) + node->external_signal(node->value()); + } + }; + + auto internal_tokens = std::tuple{args.subscribe(detail::internal_tag{}, internal_subscriber)...}; + auto external_tokens = std::tuple{args.subscribe(external_subscriber)...}; + + node->getter = [ + func = std::move(func), + internal_tokens = std::move(internal_tokens), + external_tokens = std::move(external_tokens), + args...]() -> R { + return func(*args...); + }; + + return value(detail::internal_tag{}, std::move(node)); + } + +} diff --git a/libs/react/include/psemek/react/source.hpp b/libs/react/include/psemek/react/source.hpp new file mode 100644 index 00000000..afc99963 --- /dev/null +++ b/libs/react/include/psemek/react/source.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include + +namespace psemek::react +{ + + template + struct source + : value + { + source(T x) + : value(detail::internal_tag{}, std::make_shared>()) + { + set(std::move(x)); + } + + void set(T x) const + { + this->node_->cached_value = std::nullopt; + this->node_->getter = [x = std::move(x)]() mutable { return std::move(x); }; + this->node_->internal_signal(); + this->node_->external_signal(this->node_->value()); + } + }; + + template <> + struct source + : value + { + source() + : value(detail::internal_tag{}, std::make_shared>()) + {} + + void set() const + { + this->node_->internal_signal(); + this->node_->external_signal(); + } + }; + +} diff --git a/libs/react/include/psemek/react/value.hpp b/libs/react/include/psemek/react/value.hpp new file mode 100644 index 00000000..1dea6948 --- /dev/null +++ b/libs/react/include/psemek/react/value.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include + +#include +#include + +namespace psemek::react +{ + + namespace detail + { + + struct internal_tag + {}; + + template + struct node + { + util::signal internal_signal; + util::signal external_signal; + util::function getter; + std::optional cached_value; + + T const & value() + { + if (!cached_value) + cached_value = getter(); + return *cached_value; + } + }; + + template <> + struct node + { + util::signal internal_signal; + util::signal external_signal; + + void value() + {} + }; + + template + struct node_value_type + { + using type = T const &; + }; + + template <> + struct node_value_type + { + using type = void; + }; + + } + + template + struct value + { + value(detail::internal_tag, std::shared_ptr> node) + : node_(node) + {} + + typename detail::node_value_type::type operator *() const + { + return node_->value(); + } + + [[nodiscard]] auto subscribe(typename util::signal::subscriber subscriber, bool call = false) const + { + if (call) + { + if constexpr (std::is_same_v) + subscriber(); + else + subscriber(node_->value()); + } + return node_->external_signal.subscribe(std::move(subscriber)); + } + + [[nodiscard]] auto subscribe(detail::internal_tag, std::function subscriber) const + { + return node_->internal_signal.subscribe(std::move(subscriber)); + } + + protected: + std::shared_ptr> node_; + }; + +}