Add reactive expressions library

This commit is contained in:
Nikita Lisitsa 2023-02-24 15:01:53 +03:00
parent 15b08aeb47
commit 9b304ead00
4 changed files with 184 additions and 0 deletions

View file

@ -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)

View file

@ -0,0 +1,46 @@
#pragma once
#include <psemek/react/value.hpp>
namespace psemek::react
{
template <typename F, typename ... Args>
auto map(F func, value<Args> ... args)
{
using R = decltype(func(std::declval<Args const &>()...));
auto node = std::make_shared<detail::node<R>>();
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<R>(detail::internal_tag{}, std::move(node));
}
}

View file

@ -0,0 +1,42 @@
#pragma once
#include <psemek/react/value.hpp>
namespace psemek::react
{
template <typename T>
struct source
: value<T>
{
source(T x)
: value<T>(detail::internal_tag{}, std::make_shared<detail::node<T>>())
{
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<void>
: value<void>
{
source()
: value<void>(detail::internal_tag{}, std::make_shared<detail::node<void>>())
{}
void set() const
{
this->node_->internal_signal();
this->node_->external_signal();
}
};
}

View file

@ -0,0 +1,90 @@
#pragma once
#include <psemek/util/signal.hpp>
#include <optional>
#include <memory>
namespace psemek::react
{
namespace detail
{
struct internal_tag
{};
template <typename T>
struct node
{
util::signal<void> internal_signal;
util::signal<T> external_signal;
util::function<T()> getter;
std::optional<T> cached_value;
T const & value()
{
if (!cached_value)
cached_value = getter();
return *cached_value;
}
};
template <>
struct node<void>
{
util::signal<void> internal_signal;
util::signal<void> external_signal;
void value()
{}
};
template <typename T>
struct node_value_type
{
using type = T const &;
};
template <>
struct node_value_type<void>
{
using type = void;
};
}
template <typename T>
struct value
{
value(detail::internal_tag, std::shared_ptr<detail::node<T>> node)
: node_(node)
{}
typename detail::node_value_type<T>::type operator *() const
{
return node_->value();
}
[[nodiscard]] auto subscribe(typename util::signal<T>::subscriber subscriber, bool call = false) const
{
if (call)
{
if constexpr (std::is_same_v<T, void>)
subscriber();
else
subscriber(node_->value());
}
return node_->external_signal.subscribe(std::move(subscriber));
}
[[nodiscard]] auto subscribe(detail::internal_tag, std::function<void()> subscriber) const
{
return node_->internal_signal.subscribe(std::move(subscriber));
}
protected:
std::shared_ptr<detail::node<T>> node_;
};
}