init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
# This file is part of Desktop App Toolkit,
# a set of libraries for developing nice desktop applications.
#
# For license and copyright information please follow this link:
# https://github.com/desktop-app/legal/blob/master/LEGAL
add_library(lib_rpl INTERFACE)
add_library(desktop-app::lib_rpl ALIAS lib_rpl)
get_filename_component(src_loc "." REALPATH)
nice_target_sources(lib_rpl ${src_loc}
INTERFACE
rpl/details/callable.h
rpl/details/superset_type.h
rpl/details/type_list.h
rpl/after_next.h
rpl/before_next.h
rpl/combine.h
rpl/combine_previous.h
rpl/complete.h
rpl/conditional.h
rpl/consumer.h
rpl/deferred.h
rpl/distinct_until_changed.h
rpl/event_stream.h
rpl/fail.h
rpl/filter.h
rpl/flatten_latest.h
rpl/lifetime.h
rpl/map.h
rpl/mappers.h
rpl/merge.h
rpl/never.h
rpl/producer.h
rpl/range.h
rpl/rpl.h
rpl/skip.h
rpl/take.h
rpl/then.h
rpl/type_erased.h
rpl/variable.h
)
target_include_directories(lib_rpl
INTERFACE
${src_loc}
)
target_link_libraries(lib_rpl
INTERFACE
desktop-app::external_gsl
)

View File

@@ -0,0 +1,58 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <rpl/producer.h>
namespace rpl {
namespace details {
template <typename SideEffect>
class after_next_helper {
public:
template <typename OtherSideEffect>
after_next_helper(OtherSideEffect &&method)
: _method(std::forward<OtherSideEffect>(method)) {
}
template <typename Value, typename Error, typename Generator>
auto operator()(producer<Value, Error, Generator> &&initial) {
return make_producer<Value, Error>([
initial = std::move(initial),
method = std::move(_method)
](const auto &consumer) mutable {
return std::move(initial).start(
[method = std::move(method), consumer](auto &&value) {
auto copy = method;
consumer.put_next_copy(value);
details::callable_invoke(
std::move(copy),
std::forward<decltype(value)>(value));
}, [consumer](auto &&error) {
consumer.put_error_forward(
std::forward<decltype(error)>(error));
}, [consumer] {
consumer.put_done();
});
});
}
private:
SideEffect _method;
};
} // namespace details
template <typename SideEffect>
inline auto after_next(SideEffect &&method)
-> details::after_next_helper<std::decay_t<SideEffect>> {
return details::after_next_helper<std::decay_t<SideEffect>>(
std::forward<SideEffect>(method));
}
} // namespace rpl

View File

@@ -0,0 +1,23 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <rpl/producer.h>
#include <rpl/filter.h>
namespace rpl {
template <typename SideEffect>
inline auto before_next(SideEffect &&method) {
return filter([method = std::forward<SideEffect>(method)](
const auto &value) {
details::callable_invoke(method, value);
return true;
});
}
} // namespace rpl

View File

@@ -0,0 +1,348 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/optional.h"
#include "base/variant.h"
#include <rpl/map.h>
#include <rpl/producer.h>
#include <rpl/details/type_list.h>
#include <rpl/details/callable.h>
#include <rpl/mappers.h>
#include <rpl/complete.h>
namespace rpl {
namespace details {
template <typename ...Values>
struct combine_state {
combine_state() : accumulated(std::tuple<std::optional<Values>...>()) {
}
std::optional<std::tuple<std::optional<Values>...>> accumulated;
std::optional<std::tuple<Values...>> latest;
int invalid = sizeof...(Values);
int working = sizeof...(Values);
};
template <typename ...Values, std::size_t ...I>
inline std::tuple<Values...> combine_make_first(
std::tuple<std::optional<Values>...> &&accumulated,
std::index_sequence<I...>) {
return std::make_tuple(std::move(*std::get<I>(accumulated))...);
}
template <size_t Index, typename consumer_type, typename ...Values>
class combine_subscribe_one {
public:
combine_subscribe_one(
const consumer_type &consumer,
combine_state<Values...> *state)
: _consumer(consumer)
, _state(state) {
}
template <typename Value, typename Error, typename Generator>
void subscribe(producer<Value, Error, Generator> &&producer) {
_consumer.add_lifetime(std::move(producer).start(
[consumer = _consumer, state = _state](Value &&value) {
if (!state->accumulated) {
std::get<Index>(*state->latest) = std::move(value);
consumer.put_next_copy(*state->latest);
} else {
auto &accumulated = std::get<Index>(
*state->accumulated);
if (accumulated) {
accumulated = std::move(value);
} else {
accumulated = std::move(value);
if (!--state->invalid) {
constexpr auto kArity = sizeof...(Values);
state->latest = combine_make_first(
std::move(*state->accumulated),
std::make_index_sequence<kArity>());
state->accumulated = std::nullopt;
consumer.put_next_copy(*state->latest);
}
}
}
}, [consumer = _consumer](auto &&error) {
consumer.put_error_forward(
std::forward<decltype(error)>(error));
}, [consumer = _consumer, state = _state] {
if (!--state->working) {
consumer.put_done();
}
}));
}
private:
const consumer_type &_consumer;
combine_state<Values...> *_state = nullptr;
};
template <
typename consumer_type,
typename ...Values,
typename ...Errors,
typename ...Generators,
std::size_t ...I>
inline void combine_subscribe(
const consumer_type &consumer,
combine_state<Values...> *state,
std::index_sequence<I...>,
std::tuple<producer<Values, Errors, Generators>...> &&saved) {
auto consume = { (
combine_subscribe_one<I, consumer_type, Values...>(
consumer,
state
).subscribe(std::get<I>(std::move(saved))), 0)... };
(void)consume;
}
template <typename ...Producers>
class combine_implementation_helper;
template <typename ...Producers>
combine_implementation_helper<std::decay_t<Producers>...>
make_combine_implementation_helper(Producers &&...producers) {
return combine_implementation_helper<std::decay_t<Producers>...>(
std::forward<Producers>(producers)...);
}
template <
typename ...Values,
typename ...Errors,
typename ...Generators>
class combine_implementation_helper<producer<Values, Errors, Generators>...> {
public:
using CombinedValue = std::tuple<Values...>;
using CombinedError = v::normalized_variant_t<Errors...>;
combine_implementation_helper(
producer<Values, Errors, Generators> &&...producers)
: _saved(std::make_tuple(std::move(producers)...)) {
}
template <typename Handlers>
lifetime operator()(const consumer<CombinedValue, CombinedError, Handlers> &consumer) {
auto state = consumer.template make_state<
combine_state<Values...>>();
constexpr auto kArity = sizeof...(Values);
combine_subscribe(
consumer,
state,
std::make_index_sequence<kArity>(),
std::move(_saved));
return lifetime();
}
private:
std::tuple<producer<Values, Errors, Generators>...> _saved;
};
template <
typename ...Values,
typename ...Errors,
typename ...Generators>
inline auto combine_implementation(
producer<Values, Errors, Generators> &&...producers) {
using CombinedValue = std::tuple<Values...>;
using CombinedError = v::normalized_variant_t<Errors...>;
return make_producer<CombinedValue, CombinedError>(
make_combine_implementation_helper(std::move(producers)...));
}
template <typename ...Args>
struct combine_just_producers : std::false_type {
};
template <typename ...Args>
constexpr bool combine_just_producers_v
= combine_just_producers<Args...>::value;
template <
typename ...Values,
typename ...Errors,
typename ...Generators>
struct combine_just_producers<
producer<Values, Errors, Generators>...>
: std::true_type {
};
template <typename ArgsList>
struct combine_just_producers_list
: type_list::extract_to_t<ArgsList, combine_just_producers> {
};
template <typename ...Args>
struct combine_result_type;
template <typename ...Args>
using combine_result_type_t
= typename combine_result_type<Args...>::type;
template <
typename ...Values,
typename ...Errors,
typename ...Generators>
struct combine_result_type<producer<Values, Errors, Generators>...> {
using type = std::tuple<Values...>;
};
template <typename ArgsList>
struct combine_result_type_list
: type_list::extract_to_t<ArgsList, combine_result_type> {
};
template <typename ArgsList>
using combine_result_type_list_t
= typename combine_result_type_list<ArgsList>::type;
template <typename ArgsList>
using combine_producers_no_mapper_t
= type_list::chop_last_t<ArgsList>;
template <typename ArgsList>
constexpr bool combine_is_good_mapper(std::true_type) {
return is_callable_v<
type_list::last_t<ArgsList>,
combine_result_type_list_t<
combine_producers_no_mapper_t<ArgsList>
>>;
}
template <typename ArgsList>
constexpr bool combine_is_good_mapper(std::false_type) {
return false;
}
template <typename ArgsList>
struct combine_producers_with_mapper_list : std::bool_constant<
combine_is_good_mapper<ArgsList>(
combine_just_producers_list<
combine_producers_no_mapper_t<ArgsList>
>())> {
};
template <typename ...Args>
struct combine_producers_with_mapper
: combine_producers_with_mapper_list<type_list::list<Args...>> {
};
template <typename ...Args>
constexpr bool combine_producers_with_mapper_v
= combine_producers_with_mapper<Args...>::value;
template <typename ...Producers, std::size_t ...I>
inline decltype(auto) combine_call(
std::index_sequence<I...>,
Producers &&...producers) {
return combine_implementation(
argument_mapper<I>::call(std::move(producers)...)...);
}
} // namespace details
template <
typename ...Args,
typename = std::enable_if_t<
details::combine_just_producers_v<Args...>
|| details::combine_producers_with_mapper_v<Args...>>>
inline decltype(auto) combine(Args &&...args) {
if constexpr (details::combine_just_producers_v<Args...>) {
return details::combine_implementation(std::move(args)...);
} else if constexpr (details::combine_producers_with_mapper_v<Args...>) {
constexpr auto kProducersCount = sizeof...(Args) - 1;
return details::combine_call(
std::make_index_sequence<kProducersCount>(),
std::forward<Args>(args)...)
| map(details::argument_mapper<kProducersCount>::call(
std::forward<Args>(args)...));
} else {
static_assert(false_(args...), "Bad combine() call.");
}
}
namespace details {
template <typename Value>
struct combine_vector_state {
std::vector<std::optional<Value>> accumulated;
std::vector<Value> latest;
int invalid = 0;
int working = 0;
};
} // namespace details
template <typename Value, typename Error, typename Generator>
inline auto combine(
std::vector<producer<Value, Error, Generator>> &&producers) {
using state_type = details::combine_vector_state<Value>;
return make_producer<std::vector<Value>, Error>([
producers = std::move(producers)
](const auto &consumer) mutable {
auto count = producers.size();
auto state = consumer.template make_state<state_type>();
state->accumulated.resize(count);
state->invalid = count;
state->working = count;
for (auto index = 0; index != count; ++index) {
auto &producer = producers[index];
consumer.add_lifetime(std::move(producer).start(
[consumer, state, index](Value &&value) {
if (state->accumulated.empty()) {
state->latest[index] = std::move(value);
consumer.put_next_copy(state->latest);
} else if (state->accumulated[index]) {
state->accumulated[index] = std::move(value);
} else {
state->accumulated[index] = std::move(value);
if (!--state->invalid) {
state->latest.reserve(
state->accumulated.size());
for (auto &&value : state->accumulated) {
state->latest.push_back(
std::move(*value));
}
details::take(state->accumulated);
consumer.put_next_copy(state->latest);
}
}
}, [consumer](auto &&error) {
consumer.put_error_forward(
std::forward<decltype(error)>(error));
}, [consumer, state] {
if (!--state->working) {
consumer.put_done();
}
}));
}
if (!count) {
consumer.put_done();
}
return lifetime();
});
}
template <
typename Value,
typename Error,
typename Generator,
typename Mapper>
inline auto combine(
std::vector<producer<Value, Error, Generator>> &&producers,
Mapper &&mapper) {
return combine(std::move(producers))
| map(std::forward<Mapper>(mapper));
}
} // namespace rpl

View File

@@ -0,0 +1,106 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <rpl/producer.h>
#include "base/optional.h"
namespace rpl {
namespace details {
class combine_previous_helper {
public:
template <typename Value, typename Error, typename Generator>
auto operator()(
producer<Value, Error, Generator> &&initial) const {
return make_producer<std::tuple<Value, Value>, Error>([
initial = std::move(initial)
](const auto &consumer) mutable {
auto previous = consumer.template make_state<
std::optional<Value>
>();
return std::move(initial).start(
[consumer, previous](auto &&value) {
if (auto &exists = *previous) {
auto &existing = *exists;
auto next = std::make_tuple(
std::move(existing),
value);
consumer.put_next(std::move(next));
existing = std::forward<decltype(value)>(
value);
} else {
*previous = std::forward<decltype(value)>(
value);
}
}, [consumer](auto &&error) {
consumer.put_error_forward(std::forward<decltype(error)>(error));
}, [consumer] {
consumer.put_done();
});
});
}
};
template <typename DefaultValue>
class combine_previous_with_default_helper {
public:
template <typename OtherValue>
combine_previous_with_default_helper(OtherValue &&value)
: _value(std::forward<OtherValue>(value)) {
}
template <typename Value, typename Error, typename Generator>
auto operator()(producer<Value, Error, Generator> &&initial) {
return make_producer<std::tuple<Value, Value>, Error>([
initial = std::move(initial),
value = Value(std::move(_value))
](const auto &consumer) mutable {
auto previous = consumer.template make_state<Value>(
std::move(value));
return std::move(initial).start(
[consumer, previous](auto &&value) {
auto &existing = *previous;
auto next = std::make_tuple(
std::move(existing),
value);
consumer.put_next(std::move(next));
existing = std::forward<decltype(value)>(value);
}, [consumer](auto &&error) {
consumer.put_error_forward(std::forward<decltype(error)>(error));
}, [consumer] {
consumer.put_done();
});
});
}
private:
DefaultValue _value;
};
template <typename DefaultValue>
combine_previous_with_default_helper<std::decay_t<DefaultValue>>
combine_previous_with_default(DefaultValue &&value) {
return { std::forward<DefaultValue>(value) };
}
} // namespace details
inline auto combine_previous()
-> details::combine_previous_helper {
return details::combine_previous_helper();
}
template <typename DefaultValue>
inline auto combine_previous(DefaultValue &&value) {
return details::combine_previous_with_default(
std::forward<DefaultValue>(value));
}
} // namespace rpl

View File

@@ -0,0 +1,21 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <rpl/producer.h>
namespace rpl {
template <typename Value = empty_value, typename Error = no_error>
inline auto complete() {
return make_producer<Value, Error>([](const auto &consumer) {
consumer.put_done();
return lifetime();
});
}
} // namespace rpl

View File

@@ -0,0 +1,51 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/optional.h"
#include "base/variant.h"
#include <rpl/map.h>
#include <rpl/producer.h>
namespace rpl {
template <
typename Value,
typename Error,
typename GeneratorTest,
typename GeneratorA,
typename GeneratorB>
inline auto conditional(
rpl::producer<bool, Error, GeneratorTest> &&test,
rpl::producer<Value, Error, GeneratorA> &&a,
rpl::producer<Value, Error, GeneratorB> &&b) {
return rpl::combine(
std::move(test),
std::move(a),
std::move(b)
) | rpl::map([](bool test, Value &&a, Value &&b) {
return test ? std::move(a) : std::move(b);
});
//struct conditional_state {
// std::optional<Value> a;
// std::optional<Value> b;
// char state = -1;
// int working = 3;
//};
//return rpl::make_producer<Value, Error>([
// test = std::move(test),
// a = std::move(a),
// b = std::move(b)
//](const auto &consumer) mutable {
// auto result = lifetime();
// const auto state = result.make_state<conditional_state>();
// result.add(std::move(test).start())
// return result;
//});
}
} // namespace rpl

View File

@@ -0,0 +1,637 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <memory>
#include <gsl/assert>
#include <rpl/lifetime.h>
#include <rpl/details/callable.h>
// GCC 7.2 can't handle not type-erased consumers.
// It eats up 4GB RAM + 16GB swap on the unittest and dies.
// Clang and Visual C++ both handle it without such problems.
#if defined _DEBUG || defined COMPILER_GCC
#define RPL_CONSUMER_TYPE_ERASED_ALWAYS
#endif // _DEBUG || COMPILER_GCC
namespace rpl {
namespace details {
template <
typename Value,
typename Error,
typename OnNext,
typename OnError,
typename OnDone>
class consumer_handlers;
template <typename Value, typename Error>
class type_erased_handlers {
public:
virtual bool put_next(Value &&value) = 0;
virtual bool put_next_copy(const Value &value) = 0;
virtual void put_error(Error &&error) = 0;
virtual void put_error_copy(const Error &error) = 0;
virtual void put_done() = 0;
bool add_lifetime(lifetime &&lifetime);
template <typename Type, typename... Args>
Type *make_state(Args&& ...args);
void terminate();
virtual ~type_erased_handlers() = default;
protected:
lifetime _lifetime;
bool _terminated = false;
};
template <typename Handlers>
struct is_type_erased_handlers
: std::false_type {
};
template <typename Value, typename Error>
struct is_type_erased_handlers<type_erased_handlers<Value, Error>>
: std::true_type {
};
template <typename Handlers>
constexpr bool is_type_erased_handlers_v
= is_type_erased_handlers<Handlers>::value;
template <typename Value, typename Error, typename OnNext, typename OnError, typename OnDone>
class consumer_handlers final
: public type_erased_handlers<Value, Error> {
public:
template <
typename OnNextOther,
typename OnErrorOther,
typename OnDoneOther>
consumer_handlers(
OnNextOther &&next,
OnErrorOther &&error,
OnDoneOther &&done)
: _next(std::forward<OnNextOther>(next))
, _error(std::forward<OnErrorOther>(error))
, _done(std::forward<OnDoneOther>(done)) {
}
bool put_next(Value &&value) final override;
bool put_next_copy(const Value &value) final override;
void put_error(Error &&error) final override;
void put_error_copy(const Error &error) final override;
void put_done() final override;
private:
OnNext _next;
OnError _error;
OnDone _done;
};
template <typename Value, typename Error>
inline bool type_erased_handlers<Value, Error>::add_lifetime(
lifetime &&lifetime) {
if (_terminated) {
lifetime.destroy();
return false;
}
_lifetime.add(std::move(lifetime));
return true;
}
template <typename Value, typename Error>
template <typename Type, typename... Args>
inline Type *type_erased_handlers<Value, Error>::make_state(
Args&& ...args) {
if (_terminated) {
return nullptr;
}
return _lifetime.make_state<Type>(std::forward<Args>(args)...);
}
template <typename Value, typename Error>
inline void type_erased_handlers<Value, Error>::terminate() {
if (!_terminated) {
_terminated = true;
_lifetime.destroy();
}
}
template <
typename Value,
typename Error,
typename OnNext,
typename OnError,
typename OnDone>
bool consumer_handlers<
Value,
Error,
OnNext,
OnError,
OnDone
>::put_next(Value &&value) {
if (this->_terminated) {
return false;
}
auto handler = this->_next;
details::callable_invoke(std::move(handler), std::move(value));
return true;
}
template <
typename Value,
typename Error,
typename OnNext,
typename OnError,
typename OnDone>
bool consumer_handlers<
Value,
Error,
OnNext,
OnError,
OnDone
>::put_next_copy(const Value &value) {
if (this->_terminated) {
return false;
}
auto handler = this->_next;
details::const_ref_call_invoke(std::move(handler), value);
return true;
}
template <
typename Value,
typename Error,
typename OnNext,
typename OnError,
typename OnDone>
void consumer_handlers<
Value,
Error,
OnNext,
OnError,
OnDone
>::put_error(Error &&error) {
if (!this->_terminated) {
details::callable_invoke(
std::move(this->_error),
std::move(error));
this->terminate();
}
}
template <
typename Value,
typename Error,
typename OnNext,
typename OnError,
typename OnDone>
void consumer_handlers<
Value,
Error,
OnNext,
OnError,
OnDone
>::put_error_copy(const Error &error) {
if (!this->_terminated) {
details::const_ref_call_invoke(
std::move(this->_error),
error);
this->terminate();
}
}
template <
typename Value,
typename Error,
typename OnNext,
typename OnError,
typename OnDone>
void consumer_handlers<
Value,
Error,
OnNext,
OnError,
OnDone
>::put_done() {
if (!this->_terminated) {
std::move(this->_done)();
this->terminate();
}
}
} // namespace details
struct no_value {
no_value() = delete;
};
struct no_error {
no_error() = delete;
};
struct empty_value {
};
struct empty_error {
};
inline constexpr empty_value empty{};
template <
typename Value = empty_value,
typename Error = no_error,
typename Handlers = details::type_erased_handlers<Value, Error>>
class consumer;
namespace details {
template <typename Value, typename Error, typename Handlers>
class consumer_base {
static constexpr bool is_type_erased
= is_type_erased_handlers_v<Handlers>;
public:
template <
typename OnNext,
typename OnError,
typename OnDone>
consumer_base(
OnNext &&next,
OnError &&error,
OnDone &&done);
bool put_next(Value &&value) const;
bool put_next_copy(const Value &value) const;
bool put_next_forward(Value &&value) const {
return put_next(std::move(value));
}
bool put_next_forward(const Value &value) const {
return put_next_copy(value);
}
void put_error(Error &&error) const;
void put_error_copy(const Error &error) const;
void put_error_forward(Error &&error) const {
return put_error(std::move(error));
}
void put_error_forward(const Error &error) const {
return put_error_copy(error);
}
void put_done() const;
bool add_lifetime(lifetime &&lifetime) const;
template <typename Type, typename... Args>
Type *make_state(Args&& ...args) const;
void terminate() const;
auto terminator() const {
return [self = *this] {
self.terminate();
};
}
const details::type_erased_handlers<Value, Error> *comparable() const {
return _handlers.get();
}
private:
template <
typename OtherHandlers,
typename = std::enable_if_t<
std::is_base_of_v<Handlers, OtherHandlers>>>
consumer_base(const std::shared_ptr<OtherHandlers> &handlers)
: _handlers(handlers) {
}
template <
typename OtherHandlers,
typename = std::enable_if_t<
std::is_base_of_v<Handlers, OtherHandlers>>>
consumer_base(std::shared_ptr<OtherHandlers> &&handlers)
: _handlers(std::move(handlers)) {
}
mutable std::shared_ptr<Handlers> _handlers;
bool handlers_put_next(Value &&value) const {
if constexpr (is_type_erased) {
return _handlers->put_next(std::move(value));
} else {
return _handlers->Handlers::put_next(std::move(value));
}
}
bool handlers_put_next_copy(const Value &value) const {
if constexpr (is_type_erased) {
return _handlers->put_next_copy(value);
} else {
return _handlers->Handlers::put_next_copy(value);
}
}
std::shared_ptr<Handlers> take_handlers() const {
return std::exchange(_handlers, nullptr);
}
template <
typename OtherValue,
typename OtherError,
typename OtherHandlers>
friend class ::rpl::consumer;
};
template <typename Value, typename Error, typename Handlers>
template <typename OnNext, typename OnError, typename OnDone>
inline consumer_base<Value, Error, Handlers>::consumer_base(
OnNext &&next,
OnError &&error,
OnDone &&done)
: _handlers(std::make_shared<consumer_handlers<
Value,
Error,
std::decay_t<OnNext>,
std::decay_t<OnError>,
std::decay_t<OnDone>>>(
std::forward<OnNext>(next),
std::forward<OnError>(error),
std::forward<OnDone>(done))) {
}
template <typename Value, typename Error, typename Handlers>
inline bool consumer_base<Value, Error, Handlers>::put_next(
Value &&value) const {
if (_handlers) {
if (handlers_put_next(std::move(value))) {
return true;
}
_handlers = nullptr;
}
return false;
}
template <typename Value, typename Error, typename Handlers>
inline bool consumer_base<Value, Error, Handlers>::put_next_copy(
const Value &value) const {
if (_handlers) {
if (handlers_put_next_copy(value)) {
return true;
}
_handlers = nullptr;
}
return false;
}
template <typename Value, typename Error, typename Handlers>
inline void consumer_base<Value, Error, Handlers>::put_error(
Error &&error) const {
if (_handlers) {
if constexpr (is_type_erased) {
take_handlers()->put_error(std::move(error));
} else {
take_handlers()->Handlers::put_error(std::move(error));
}
}
}
template <typename Value, typename Error, typename Handlers>
inline void consumer_base<Value, Error, Handlers>::put_error_copy(
const Error &error) const {
if (_handlers) {
if constexpr (is_type_erased) {
take_handlers()->put_error_copy(error);
} else {
take_handlers()->Handlers::put_error_copy(error);
}
}
}
template <typename Value, typename Error, typename Handlers>
inline void consumer_base<Value, Error, Handlers>::put_done() const {
if (_handlers) {
if constexpr (is_type_erased) {
take_handlers()->put_done();
} else {
take_handlers()->Handlers::put_done();
}
}
}
template <typename Value, typename Error, typename Handlers>
inline bool consumer_base<Value, Error, Handlers>::add_lifetime(
lifetime &&lifetime) const {
if (!_handlers) {
lifetime.destroy();
return false;
}
if (_handlers->add_lifetime(std::move(lifetime))) {
return true;
}
_handlers = nullptr;
return false;
}
template <typename Value, typename Error, typename Handlers>
template <typename Type, typename... Args>
inline Type *consumer_base<Value, Error, Handlers>::make_state(
Args&& ...args) const {
if (!_handlers) {
return nullptr;
}
if (auto result = _handlers->template make_state<Type>(
std::forward<Args>(args)...)) {
return result;
}
_handlers = nullptr;
return nullptr;
}
template <typename Value, typename Error, typename Handlers>
inline void consumer_base<Value, Error, Handlers>::terminate() const {
if (_handlers) {
std::exchange(_handlers, nullptr)->terminate();
}
}
template <typename Value, typename Error>
using consumer_base_type_erased = consumer_base<
Value,
Error,
details::type_erased_handlers<Value, Error>>;
template <typename Value, typename Error, typename Handlers>
constexpr bool is_specific_handlers_v = !std::is_same_v<
details::type_erased_handlers<Value, Error>,
Handlers
> && std::is_base_of_v<
details::type_erased_handlers<Value, Error>,
Handlers
>;
} // namespace details
template <typename Value, typename Error, typename Handlers>
class consumer final
: public details::consumer_base<Value, Error, Handlers> {
using parent_type = details::consumer_base<
Value,
Error,
Handlers>;
public:
using parent_type::parent_type;
};
template <typename Value, typename Error>
class consumer<Value, Error, details::type_erased_handlers<Value, Error>> final
: public details::consumer_base_type_erased<Value, Error> {
using parent_type = details::consumer_base_type_erased<
Value,
Error>;
public:
using parent_type::parent_type;
template <
typename Handlers,
typename = std::enable_if_t<
details::is_specific_handlers_v<Value, Error, Handlers>>>
consumer(const details::consumer_base<Value, Error, Handlers> &other)
: parent_type(other._handlers) {
}
template <
typename Handlers,
typename = std::enable_if_t<
details::is_specific_handlers_v<Value, Error, Handlers>>>
consumer(details::consumer_base<Value, Error, Handlers> &&other)
: parent_type(std::move(other._handlers)) {
}
template <
typename Handlers,
typename = std::enable_if_t<
details::is_specific_handlers_v<Value, Error, Handlers>>>
consumer &operator=(
const details::consumer_base<Value, Error, Handlers> &other) {
this->_handlers = other._handlers;
return *this;
}
template <
typename Handlers,
typename = std::enable_if_t<
details::is_specific_handlers_v<Value, Error, Handlers>>>
consumer &operator=(
details::consumer_base<Value, Error, Handlers> &&other) {
this->_handlers = std::move(other._handlers);
return *this;
}
};
template <
typename Value,
typename Error,
typename Handlers1,
typename Handlers2>
inline bool operator==(
const consumer<Value, Error, Handlers1> &a,
const consumer<Value, Error, Handlers2> &b) {
return a.comparable() == b.comparable();
}
template <
typename Value,
typename Error,
typename Handlers1,
typename Handlers2>
inline bool operator<(
const consumer<Value, Error, Handlers1> &a,
const consumer<Value, Error, Handlers2> &b) {
return a.comparable() < b.comparable();
}
template <
typename Value,
typename Error,
typename Handlers1,
typename Handlers2>
inline bool operator!=(
const consumer<Value, Error, Handlers1> &a,
const consumer<Value, Error, Handlers2> &b) {
return !(a == b);
}
template <
typename Value,
typename Error,
typename Handlers1,
typename Handlers2>
inline bool operator>(
const consumer<Value, Error, Handlers1> &a,
const consumer<Value, Error, Handlers2> &b) {
return b < a;
}
template <
typename Value,
typename Error,
typename Handlers1,
typename Handlers2>
inline bool operator<=(
const consumer<Value, Error, Handlers1> &a,
const consumer<Value, Error, Handlers2> &b) {
return !(b < a);
}
template <
typename Value,
typename Error,
typename Handlers1,
typename Handlers2>
inline bool operator>=(
const consumer<Value, Error, Handlers1> &a,
const consumer<Value, Error, Handlers2> &b) {
return !(a < b);
}
template <
typename Value,
typename Error,
typename OnNext,
typename OnError,
typename OnDone,
typename = std::enable_if_t<
details::is_callable_v<OnNext, Value> &&
details::is_callable_v<OnError, Error> &&
details::is_callable_v<OnDone>>>
#ifdef RPL_CONSUMER_TYPE_ERASED_ALWAYS
inline consumer<Value, Error> make_consumer(
#else // RPL_CONSUMER_TYPE_ERASED_ALWAYS
inline auto make_consumer(
#endif // !RPL_CONSUMER_TYPE_ERASED_ALWAYS
OnNext &&next,
OnError &&error,
OnDone &&done) {
return consumer<Value, Error, details::consumer_handlers<
Value,
Error,
std::decay_t<OnNext>,
std::decay_t<OnError>,
std::decay_t<OnDone>>>(
std::forward<OnNext>(next),
std::forward<OnError>(error),
std::forward<OnDone>(done));
}
} // namespace rpl

View File

@@ -0,0 +1,25 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <rpl/producer.h>
namespace rpl {
template <
typename Creator,
typename Value = typename decltype(std::declval<Creator>()())::value_type,
typename Error = typename decltype(std::declval<Creator>()())::error_type>
inline auto deferred(Creator &&creator) {
return make_producer<Value, Error>([
creator = std::forward<Creator>(creator)
](const auto &consumer) mutable {
return std::move(creator)().start_existing(consumer);
});
}
} // namespace rpl

View File

@@ -0,0 +1,146 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/build_config.h"
#include <tuple>
namespace rpl {
namespace details {
template <typename Arg>
const Arg &const_ref_val() noexcept;
template <typename Arg>
Arg &lvalue_ref_val() noexcept;
using false_t = char;
struct true_t {
false_t data[2];
};
static_assert(sizeof(false_t) != sizeof(true_t), "I can't work :(");
template <
typename Method,
typename ...Args,
typename = decltype(std::invoke(
std::declval<Method>(),
std::declval<Args>()...))>
true_t test_callable_plain(Method &&, Args &&...) noexcept;
false_t test_callable_plain(...) noexcept;
template <typename Method, typename ...Args>
struct is_callable_plain
: std::bool_constant<(
sizeof(test_callable_plain(
std::declval<Method>(),
std::declval<Args>()...
)) == sizeof(true_t))> {
};
template <typename Method, typename ...Args>
constexpr bool is_callable_plain_v = is_callable_plain<Method, Args...>::value;
template <
typename Method,
typename ...Types,
typename = decltype(std::declval<Method>()(
std::declval<Types>()...))>
true_t test_callable_tuple(
Method &&,
std::tuple<Types...> &&) noexcept;
template <
typename Method,
typename ...Types,
typename = decltype(std::declval<Method>()(
const_ref_val<Types>()...))>
true_t test_callable_tuple(
Method &&,
const std::tuple<Types...> &) noexcept;
false_t test_callable_tuple(...) noexcept;
template <typename Method, typename Arg>
constexpr bool is_callable_tuple_v = (sizeof(test_callable_tuple(
std::declval<Method>(),
std::declval<Arg>())) == sizeof(true_t));
template <typename Method, typename Arg>
struct is_callable_tuple
: std::bool_constant<
is_callable_tuple_v<Method, Arg>> {
};
template <typename Method, typename ...Args>
struct is_callable;
template <typename Method>
struct is_callable<Method>
: std::bool_constant<
is_callable_plain_v<Method>> {
};
template <typename Method, typename Arg>
struct is_callable<Method, Arg>
: std::bool_constant<
is_callable_plain_v<Method, Arg> ||
is_callable_tuple_v<Method, Arg> ||
is_callable_plain_v<Method>> {
};
template <typename Method, typename ...Args>
constexpr bool is_callable_v = is_callable<Method, Args...>::value;
template <typename Method, typename Arg>
inline decltype(auto) callable_invoke(Method &&method, Arg &&arg) {
if constexpr (is_callable_plain_v<Method, Arg>) {
return std::invoke(std::forward<Method>(method), std::forward<Arg>(arg));
} else if constexpr (is_callable_tuple_v<Method, Arg>) {
return std::apply(
std::forward<Method>(method),
std::forward<Arg>(arg));
} else if constexpr (is_callable_v<Method>) {
return std::forward<Method>(method)();
} else {
static_assert(false_(method, arg), "Bad callable_invoke() call.");
}
}
template <typename Method, typename Arg>
using callable_result = decltype(callable_invoke(
std::declval<Method>(),
std::declval<Arg>()));
template <
typename Method,
typename Arg,
typename = decltype(std::invoke(
std::declval<Method>(),
const_ref_val<std::decay_t<Arg>>()))>
true_t test_allows_const_ref(Method &&, Arg &&) noexcept;
false_t test_allows_const_ref(...) noexcept;
template <typename Method, typename Arg>
constexpr bool allows_const_ref_v = (sizeof(test_allows_const_ref(
std::declval<Method>(),
std::declval<Arg>())) == sizeof(true_t));
template <typename Method, typename Arg>
inline decltype(auto) const_ref_call_invoke(
Method &&method,
const Arg &arg) {
if constexpr (allows_const_ref_v<Method, Arg>) {
return callable_invoke(std::forward<Method>(method), arg);
} else {
auto copy = arg;
return callable_invoke(
std::forward<Method>(method),
std::move(copy));
}
}
} // namespace details
} // namespace rpl

View File

@@ -0,0 +1,22 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
namespace rpl {
template <typename Value1, typename Value2>
struct superset_type;
template <typename Value1, typename Value2>
using superset_type_t = typename superset_type<Value1, Value2>::type;
template <typename Value>
struct superset_type<Value, Value> {
using type = Value;
};
} // namespace rpl

View File

@@ -0,0 +1,179 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <type_traits>
namespace rpl {
namespace details {
namespace type_list {
template <typename ...Types>
struct list {
};
template <typename Type, typename ...Types>
struct list<Type, Types...> {
using head = Type;
using tail = list<Types...>;
};
using empty_list = list<>;
template <typename TypeList>
using head_t = typename TypeList::head;
template <typename TypeList>
using tail_t = typename TypeList::tail;
template <typename Head, typename Tail>
struct construct;
template <typename Head, typename Tail>
using construct_t = typename construct<Head, Tail>::type;
template <typename Head, typename ...Types>
struct construct<Head, list<Types...>> {
using type = list<Head, Types...>;
};
template <typename TypeList>
struct size;
template <typename TypeList>
constexpr std::size_t size_v = size<TypeList>::value;
template <typename ...Types>
struct size<list<Types...>>
: std::integral_constant<
std::size_t,
sizeof...(Types)> {
};
template <typename TypeList>
constexpr bool empty_v = (size_v<TypeList> == 0);
template <typename TypeList>
struct empty : std::bool_constant<empty_v<TypeList>> {
};
template <std::size_t Index, typename TypeList>
struct get;
template <std::size_t Index, typename TypeList>
using get_t = typename get<Index, TypeList>::type;
template <std::size_t Index, typename TypeList>
struct get {
using type = get_t<Index - 1, tail_t<TypeList>>;
};
template <typename TypeList>
struct get<0, TypeList> {
using type = head_t<TypeList>;
};
template <typename TypeList1, typename TypeList2>
struct concat;
template <typename TypeList1, typename TypeList2>
using concat_t = typename concat<TypeList1, TypeList2>::type;
template <typename ...Types1, typename ...Types2>
struct concat<list<Types1...>, list<Types2...>> {
using type = list<Types1..., Types2...>;
};
template <typename TypeList, typename Type>
struct remove_all;
template <typename TypeList, typename Type>
using remove_all_t = typename remove_all<TypeList, Type>::type;
template <typename TypeList, typename Type>
struct remove_all {
using head = head_t<TypeList>;
using tail = tail_t<TypeList>;
using clean_tail = remove_all_t<tail, Type>;
using type = std::conditional_t<
std::is_same_v<head, Type>,
clean_tail,
construct_t<head, clean_tail>>;
};
template <typename Type>
struct remove_all<empty_list, Type> {
using type = empty_list;
};
template <typename TypeList>
struct last;
template <typename TypeList>
using last_t = typename last<TypeList>::type;
template <typename TypeList>
struct last {
using type = last_t<tail_t<TypeList>>;
};
template <typename Type>
struct last<list<Type>> {
using type = Type;
};
template <typename TypeList>
struct chop_last;
template <typename TypeList>
using chop_last_t = typename chop_last<TypeList>::type;
template <typename TypeList>
struct chop_last {
using type = construct_t<
head_t<TypeList>,
chop_last_t<tail_t<TypeList>>>;
};
template <typename Type>
struct chop_last<list<Type>> {
using type = empty_list;
};
template <typename TypeList>
struct distinct;
template <typename TypeList>
using distinct_t = typename distinct<TypeList>::type;
template <typename TypeList>
struct distinct {
using type = construct_t<
head_t<TypeList>,
distinct_t<
remove_all_t<tail_t<TypeList>, head_t<TypeList>>>>;
};
template <>
struct distinct<empty_list> {
using type = empty_list;
};
template <typename TypeList, template <typename ...> typename To>
struct extract_to;
template <typename TypeList, template <typename ...> typename To>
using extract_to_t = typename extract_to<TypeList, To>::type;
template <typename ...Types, template <typename ...> typename To>
struct extract_to<list<Types...>, To> {
using type = To<Types...>;
};
} // namespace type_list
} // namespace details
} // namespace rpl

View File

@@ -0,0 +1,49 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <rpl/producer.h>
#include "base/optional.h"
namespace rpl {
namespace details {
class distinct_until_changed_helper {
public:
template <typename Value, typename Error, typename Generator>
auto operator()(
producer<Value, Error, Generator> &&initial) const {
return make_producer<Value, Error>([
initial = std::move(initial)
](const auto &consumer) mutable {
auto previous = consumer.template make_state<
std::optional<Value>
>();
return std::move(initial).start(
[consumer, previous](auto &&value) {
if (!(*previous) || (**previous) != value) {
*previous = value;
consumer.put_next_forward(std::forward<decltype(value)>(value));
}
}, [consumer](auto &&error) {
consumer.put_error_forward(std::forward<decltype(error)>(error));
}, [consumer] {
consumer.put_done();
});
});
}
};
} // namespace details
inline auto distinct_until_changed()
-> details::distinct_until_changed_helper {
return details::distinct_until_changed_helper();
}
} // namespace rpl

View File

@@ -0,0 +1,284 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <rpl/producer.h>
#include <rpl/range.h>
#include <rpl/then.h>
#include <rpl/range.h>
#include <algorithm>
#include <optional>
#include "base/assertion.h"
#include "base/index_based_iterator.h"
namespace rpl {
// Currently not thread-safe :(
template <typename Value = empty_value, typename Error = no_error>
class event_stream {
public:
event_stream() noexcept = default;
event_stream(event_stream &&other);
event_stream &operator=(event_stream &&other);
template <typename OtherValue>
void fire_forward(OtherValue &&value) const;
void fire(Value &&value) const {
return fire_forward(std::move(value));
}
void fire_copy(const Value &value) const {
return fire_forward(value);
}
template <typename OtherError>
void fire_error_forward(OtherError &&error) const;
void fire_error(Error &&error) const {
return fire_error_forward(std::move(error));
}
void fire_error_copy(const Error &error) const {
return fire_error_forward(error);
}
void fire_done() const;
#if defined _MSC_VER && _MSC_VER >= 1914 && _MSC_VER < 1916
producer<Value, Error> events() const {
#else // _MSC_VER >= 1914 && _MSC_VER < 1916
auto events() const {
#endif // _MSC_VER >= 1914 && _MSC_VER < 1916
return make_producer<Value, Error>([weak = make_weak()](
const auto &consumer) {
if (const auto strong = weak.lock()) {
auto result = [weak, consumer] {
if (const auto strong = weak.lock()) {
const auto it = std::find(
strong->consumers.begin(),
strong->consumers.end(),
consumer);
if (it != strong->consumers.end()) {
it->terminate();
}
}
};
strong->consumers.push_back(std::move(consumer));
return lifetime(std::move(result));
}
return lifetime();
});
}
auto events_starting_with(Value &&value) const {
return single<Value&&, Error>(std::move(value)) | then(events());
}
auto events_starting_with_copy(const Value &value) const {
return single<const Value&, Error>(value) | then(events());
}
bool has_consumers() const {
return (_data != nullptr) && !_data->consumers.empty();
}
~event_stream();
private:
struct Data {
std::vector<consumer<Value, Error>> consumers;
int depth = 0;
};
std::weak_ptr<Data> make_weak() const;
mutable std::shared_ptr<Data> _data;
};
template <typename Value, typename Error>
inline event_stream<Value, Error>::event_stream(event_stream &&other)
: _data(details::take(other._data)) {
}
template <typename Value, typename Error>
inline event_stream<Value, Error> &event_stream<Value, Error>::operator=(
event_stream &&other) {
if (this != &other) {
std::swap(_data, other._data);
other.fire_done();
}
return *this;
}
template <typename Value, typename Error>
template <typename OtherValue>
inline void event_stream<Value, Error>::fire_forward(
OtherValue &&value) const {
if (!_data) {
return;
}
const auto copy = _data;
auto &consumers = copy->consumers;
if (consumers.empty()) {
return;
}
++copy->depth;
const auto begin = base::index_based_begin(consumers);
const auto end = base::index_based_end(consumers);
// Copy value for every consumer except the last.
const auto prev = end - 1;
auto staleFrom = std::remove_if(begin, prev, [&](const auto &consumer) {
return !consumer.put_next_copy(value);
});
// Perhaps move value for the last consumer.
if (prev->put_next_forward(std::forward<OtherValue>(value))) {
if (staleFrom != prev) {
*staleFrom++ = std::move(*prev);
} else {
++staleFrom;
}
}
if (staleFrom != end) {
// Move new consumers.
const auto newEnd = base::index_based_end(consumers);
if (newEnd != end) {
Assert(newEnd > end);
for (auto i = end; i != newEnd;) {
*staleFrom++ = *i++;
}
}
// Erase stale consumers.
if (copy->depth == 1) {
consumers.erase(staleFrom.base(), consumers.end());
}
}
--copy->depth;
}
template <typename Value, typename Error>
template <typename OtherError>
inline void event_stream<Value, Error>::fire_error_forward(
OtherError &&error) const {
if (!_data) {
return;
}
const auto data = std::move(_data);
const auto &consumers = data->consumers;
if (consumers.empty()) {
return;
}
const auto begin = base::index_based_begin(consumers);
const auto end = base::index_based_end(consumers);
// Copy error for every consumer except the last.
const auto prev = end - 1;
std::for_each(begin, prev, [&](const auto &consumer) {
consumer.put_error_copy(error);
});
// Perhaps move error for the last consumer.
prev->put_error_forward(std::forward<OtherError>(error));
// Just drop any new consumers.
}
template <typename Value, typename Error>
void event_stream<Value, Error>::fire_done() const {
if (const auto data = details::take(_data)) {
for (const auto &consumer : data->consumers) {
consumer.put_done();
}
}
}
template <typename Value, typename Error>
inline auto event_stream<Value, Error>::make_weak() const
-> std::weak_ptr<Data> {
if (!_data) {
_data = std::make_shared<Data>();
}
return _data;
}
template <typename Value, typename Error>
inline event_stream<Value, Error>::~event_stream() {
fire_done();
}
template <typename Value, typename Error>
inline auto start_to_stream(
event_stream<Value, Error> &stream,
lifetime &alive_while) {
if constexpr (std::is_same_v<Error, no_error>) {
return on_next([&](auto &&value) {
stream.fire_forward(std::forward<decltype(value)>(value));
}, alive_while);
} else {
return on_next_error([&](auto &&value) {
stream.fire_forward(std::forward<decltype(value)>(value));
}, [&](auto &&error) {
stream.fire_error_forward(std::forward<decltype(error)>(error));
}, alive_while);
}
}
namespace details {
class start_spawning_helper {
public:
start_spawning_helper(lifetime &alive_while)
: _lifetime(alive_while) {
}
template <typename Value, typename Error, typename Generator>
auto operator()(producer<Value, Error, Generator> &&initial) {
auto stream = _lifetime.make_state<event_stream<Value, Error>>();
auto values = std::vector<Value>();
if constexpr (std::is_same_v<Error, rpl::no_error>) {
auto collecting = stream->events().start(
[&](Value &&value) { values.push_back(std::move(value)); },
[](const Error &error) {},
[] {});
std::move(initial) | start_to_stream(*stream, _lifetime);
collecting.destroy();
return vector(std::move(values)) | then(stream->events());
} else {
auto maybeError = std::optional<Error>();
auto collecting = stream->events().start(
[&](Value && value) { values.push_back(std::move(value)); },
[&](Error &&error) { maybeError = std::move(error); },
[] {});
std::move(initial) | start_to_stream(*stream, _lifetime);
collecting.destroy();
if (maybeError.has_value()) {
return rpl::producer<Value, Error>([
error = std::move(*maybeError)
](const auto &consumer) mutable {
consumer.put_error(std::move(error));
});
}
return rpl::producer<Value, Error>(vector<Value, Error>(
std::move(values)
) | then(stream->events()));
}
}
private:
lifetime &_lifetime;
};
} // namespace details
inline auto start_spawning(lifetime &alive_while)
-> details::start_spawning_helper {
return details::start_spawning_helper(alive_while);
}
} // namespace rpl

View File

@@ -0,0 +1,23 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <rpl/producer.h>
namespace rpl {
template <typename Value, typename Error>
inline auto fail(Error &&error) {
return make_producer<Value, std::decay_t<Error>>([
error = std::forward<Error>(error)
](const auto &consumer) mutable {
consumer.put_error(std::move(error));
return lifetime();
});
}
} // namespace rpl

View File

@@ -0,0 +1,176 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <rpl/producer.h>
#include <rpl/combine.h>
#include <rpl/mappers.h>
#include <gsl/pointers>
#include "base/optional.h"
namespace rpl {
namespace details {
template <typename Predicate>
class filter_helper {
public:
template <typename OtherPredicate>
filter_helper(OtherPredicate &&predicate)
: _predicate(std::forward<OtherPredicate>(predicate)) {
}
template <
typename Value,
typename Error,
typename Generator,
typename = std::enable_if_t<
details::is_callable_v<Predicate, Value>>>
auto operator()(producer<Value, Error, Generator> &&initial) {
return make_producer<Value, Error>([
initial = std::move(initial),
predicate = std::move(_predicate)
](const auto &consumer) mutable {
return std::move(initial).start(
[
consumer,
predicate = std::move(predicate)
](auto &&value) {
const auto &immutable = value;
if (details::callable_invoke(
predicate,
immutable)
) {
consumer.put_next_forward(
std::forward<decltype(value)>(value));
}
}, [consumer](auto &&error) {
consumer.put_error_forward(
std::forward<decltype(error)>(error));
}, [consumer] {
consumer.put_done();
});
});
}
private:
Predicate _predicate;
};
} // namespace details
template <typename Predicate>
inline auto filter(Predicate &&predicate)
-> details::filter_helper<std::decay_t<Predicate>> {
return details::filter_helper<std::decay_t<Predicate>>(
std::forward<Predicate>(predicate));
}
namespace details {
template <typename FilterError, typename FilterGenerator>
class filter_helper<producer<bool, FilterError, FilterGenerator>> {
public:
filter_helper(
producer<bool, FilterError, FilterGenerator> &&filterer)
: _filterer(std::move(filterer)) {
}
template <typename Value, typename Error, typename Generator>
auto operator()(producer<Value, Error, Generator> &&initial) {
using namespace mappers;
return combine(std::move(initial), std::move(_filterer))
| filter(_2)
| map(_1_of_two);
}
private:
producer<bool, FilterError, FilterGenerator> _filterer;
};
template <typename Value>
inline const Value &deref_optional_helper(
const std::optional<Value> &value) {
return *value;
}
template <typename Value>
inline Value &&deref_optional_helper(
std::optional<Value> &&value) {
return std::move(*value);
}
class filter_optional_helper {
public:
template <typename Value, typename Error, typename Generator>
auto operator()(producer<
std::optional<Value>,
Error,
Generator> &&initial) const {
return make_producer<Value, Error>([
initial = std::move(initial)
](const auto &consumer) mutable {
return std::move(initial).start(
[consumer](auto &&value) {
if (value) {
consumer.put_next_forward(
deref_optional_helper(
std::forward<decltype(value)>(
value)));
}
}, [consumer](auto &&error) {
consumer.put_error_forward(
std::forward<decltype(error)>(error));
}, [consumer] {
consumer.put_done();
});
});
}
};
class filter_nullptr_helper {
public:
template <typename Value, typename Error, typename Generator>
auto operator()(producer<
Value,
Error,
Generator> &&initial) const {
return make_producer<gsl::not_null<Value>, Error>([
initial = std::move(initial)
](const auto &consumer) mutable {
return std::move(initial).start(
[consumer](auto &&value) {
if (value) {
consumer.put_next_forward(
std::forward<decltype(value)>(
value));
}
}, [consumer](auto &&error) {
consumer.put_error_forward(
std::forward<decltype(error)>(error));
}, [consumer] {
consumer.put_done();
});
});
}
};
} // namespace details
inline auto filter_optional()
-> details::filter_optional_helper {
return details::filter_optional_helper();
}
inline auto filter_nullptr() -> details::filter_nullptr_helper {
return details::filter_nullptr_helper();
}
} // namespace rpl

View File

@@ -0,0 +1,73 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <rpl/producer.h>
namespace rpl {
namespace details {
class flatten_latest_helper {
public:
template <
typename Value,
typename Error,
typename Generator,
typename MetaGenerator>
auto operator()(producer<
producer<Value, Error, Generator>,
Error,
MetaGenerator> &&initial) const {
return make_producer<Value, Error>([
initial = std::move(initial)
](const auto &consumer) mutable {
auto state = consumer.template make_state<State>();
return std::move(initial).start(
[consumer, state](producer<Value, Error> &&inner) {
state->finished = false;
state->alive = lifetime();
std::move(inner).start(
[consumer](auto &&value) {
consumer.put_next_forward(std::forward<decltype(value)>(value));
}, [consumer](auto &&error) {
consumer.put_error_forward(std::forward<decltype(error)>(error));
}, [consumer, state] {
if (state->finished) {
consumer.put_done();
} else {
state->finished = true;
}
}, state->alive);
}, [consumer](auto &&error) {
consumer.put_error_forward(std::forward<decltype(error)>(error));
}, [consumer, state] {
if (state->finished) {
consumer.put_done();
} else {
state->finished = true;
}
});
});
}
private:
struct State {
lifetime alive;
bool finished = false;
};
};
} // namespace details
inline auto flatten_latest()
-> details::flatten_latest_helper {
return details::flatten_latest_helper();
}
} // namespace rpl

View File

@@ -0,0 +1,95 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/unique_function.h"
#include <vector>
namespace rpl {
namespace details {
template <typename Type>
inline Type take(Type &value) {
return std::exchange(value, Type{});
}
} // namespace details
class lifetime {
public:
lifetime() = default;
lifetime(lifetime &&other);
lifetime &operator=(lifetime &&other);
~lifetime() { destroy(); }
template <typename Destroy, typename = decltype(std::declval<Destroy>()())>
lifetime(Destroy &&destroy);
explicit operator bool() const { return !_callbacks.empty(); }
template <typename Destroy, typename = decltype(std::declval<Destroy>()())>
void add(Destroy &&destroy);
void add(lifetime &&other);
void destroy();
void release();
template <typename Type, typename... Args>
Type *make_state(Args&& ...args) {
const auto result = new Type(std::forward<Args>(args)...);
add([=] {
static_assert(sizeof(Type) > 0, "Can't delete unknown type.");
delete result;
});
return result;
}
private:
std::vector<base::unique_function<void()>> _callbacks;
};
inline lifetime::lifetime(lifetime &&other)
: _callbacks(details::take(other._callbacks)) {
}
inline lifetime &lifetime::operator=(lifetime &&other) {
std::swap(_callbacks, other._callbacks);
other.destroy();
return *this;
}
template <typename Destroy, typename>
inline lifetime::lifetime(Destroy &&destroy) {
_callbacks.emplace_back(std::forward<Destroy>(destroy));
}
template <typename Destroy, typename>
inline void lifetime::add(Destroy &&destroy) {
_callbacks.emplace_back(std::forward<Destroy>(destroy));
}
inline void lifetime::add(lifetime &&other) {
auto callbacks = details::take(other._callbacks);
_callbacks.insert(
_callbacks.end(),
std::make_move_iterator(callbacks.begin()),
std::make_move_iterator(callbacks.end()));
}
inline void lifetime::destroy() {
auto callbacks = details::take(_callbacks);
for (auto i = callbacks.rbegin(), e = callbacks.rend(); i != e; ++i) {
(*i)();
}
}
inline void lifetime::release() {
_callbacks.clear();
}
} // namespace rpl

248
Telegram/lib_rpl/rpl/map.h Normal file
View File

@@ -0,0 +1,248 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <rpl/producer.h>
namespace rpl {
namespace details {
template <
typename Transform,
typename NewValue,
typename Error,
typename Handlers>
class map_transform_helper {
public:
map_transform_helper(
Transform &&transform,
const consumer<NewValue, Error, Handlers> &consumer)
: _consumer(consumer)
, _transform(std::move(transform)) {
}
template <
typename OtherValue,
typename = std::enable_if_t<
std::is_rvalue_reference_v<OtherValue&&>>>
void operator()(OtherValue &&value) const {
_consumer.put_next_forward(
details::callable_invoke(_transform, std::move(value)));
}
template <
typename OtherValue,
typename = decltype(
std::declval<Transform>()(const_ref_val<OtherValue>()))>
void operator()(const OtherValue &value) const {
_consumer.put_next_forward(
details::callable_invoke(_transform, value));
}
private:
consumer<NewValue, Error, Handlers> _consumer;
Transform _transform;
};
template <
typename Transform,
typename NewValue,
typename Error,
typename Handlers,
typename = std::enable_if_t<
std::is_rvalue_reference_v<Transform&&>>>
inline map_transform_helper<Transform, NewValue, Error, Handlers>
map_transform(
Transform &&transform,
const consumer<NewValue, Error, Handlers> &consumer) {
return { std::move(transform), consumer };
}
template <typename Transform>
class map_helper {
public:
template <typename OtherTransform>
map_helper(OtherTransform &&transform)
: _transform(std::forward<OtherTransform>(transform)) {
}
template <
typename Value,
typename Error,
typename Generator,
typename NewValue = std::remove_reference_t<
details::callable_result<Transform, Value>>>
auto operator()(producer<Value, Error, Generator> &&initial) {
return make_producer<NewValue, Error>([
initial = std::move(initial),
transform = std::move(_transform)
](const auto &consumer) mutable {
return std::move(initial).start(
map_transform(
std::move(transform),
consumer
), [consumer](auto &&error) {
consumer.put_error_forward(
std::forward<decltype(error)>(error));
}, [consumer] {
consumer.put_done();
});
});
}
private:
Transform _transform;
};
} // namespace details
template <typename Transform>
inline auto map(Transform &&transform)
-> details::map_helper<std::decay_t<Transform>> {
return details::map_helper<std::decay_t<Transform>>(
std::forward<Transform>(transform));
}
template <typename Value>
[[nodiscard]] inline auto map_to(Value &&value) {
return map([value = std::forward<Value>(value)] {
return value;
});
}
inline auto to_empty = map_to(empty_value());
namespace details {
template <
typename Transform,
typename Value,
typename NewError,
typename Handlers>
class map_error_transform_helper {
public:
map_error_transform_helper(
Transform &&transform,
const consumer<Value, NewError, Handlers> &consumer)
: _transform(std::move(transform))
, _consumer(consumer) {
}
template <
typename OtherError,
typename = std::enable_if_t<
std::is_rvalue_reference_v<OtherError&&>>>
void operator()(OtherError &&error) const {
_consumer.put_error_forward(
details::callable_invoke(_transform, std::move(error)));
}
template <
typename OtherError,
typename = decltype(
std::declval<Transform>()(const_ref_val<OtherError>()))>
void operator()(const OtherError &error) const {
_consumer.put_error_forward(
details::callable_invoke(_transform, error));
}
private:
consumer<Value, NewError, Handlers> _consumer;
Transform _transform;
};
template <
typename Transform,
typename Value,
typename NewError,
typename Handlers,
typename = std::enable_if_t<
std::is_rvalue_reference_v<Transform&&>>>
inline map_error_transform_helper<Transform, Value, NewError, Handlers>
map_error_transform(
Transform &&transform,
const consumer<Value, NewError, Handlers> &consumer) {
return { std::move(transform), consumer };
}
template <typename Transform>
class map_error_helper {
public:
template <typename OtherTransform>
map_error_helper(OtherTransform &&transform)
: _transform(std::forward<OtherTransform>(transform)) {
}
template <
typename Value,
typename Error,
typename Generator,
typename NewError = std::remove_reference_t<
details::callable_result<Transform, Error>>>
auto operator()(producer<Value, Error, Generator> &&initial) {
return make_producer<Value, NewError>([
initial = std::move(initial),
transform = std::move(_transform)
](const auto &consumer) mutable {
return std::move(initial).start(
[consumer](auto &&value) {
consumer.put_next_forward(
std::forward<decltype(value)>(value));
}, map_error_transform(
std::move(transform),
consumer
), [consumer] {
consumer.put_done();
});
});
}
private:
Transform _transform;
};
class map_error_to_done_helper {
public:
map_error_to_done_helper() {
}
template <
typename Value,
typename Error,
typename Generator>
auto operator()(producer<Value, Error, Generator> &&initial) {
return make_producer<Value, no_error>([
initial = std::move(initial)
](const auto &consumer) mutable {
return std::move(initial).start(
[consumer](auto &&value) {
consumer.put_next_forward(
std::forward<decltype(value)>(value));
}, [consumer] {
consumer.put_done();
}, [consumer] {
consumer.put_done();
});
});
}
};
} // namespace details
template <typename Transform>
inline auto map_error(Transform &&transform)
-> details::map_error_helper<std::decay_t<Transform>> {
return details::map_error_helper<std::decay_t<Transform>>(
std::forward<Transform>(transform));
}
inline details::map_error_to_done_helper map_error_to_done() {
return details::map_error_to_done_helper();
}
} // namespace rpl

View File

@@ -0,0 +1,473 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
namespace rpl {
namespace details {
struct base_mapper {
};
template <typename Type>
constexpr bool is_mapper_v = std::is_base_of_v<
base_mapper,
std::decay_t<Type>>;
template <std::size_t Index>
struct argument_mapper : base_mapper {
template <
typename Arg,
typename ...Args,
typename = std::enable_if_t<(sizeof...(Args) >= Index)>>
static constexpr decltype(auto) call(Arg &&arg, Args &&...args) {
return argument_mapper<Index - 1>::call(
std::forward<Args>(args)...);
}
template <
typename ...Args,
typename = std::enable_if_t<(sizeof...(Args) > Index)>>
constexpr auto operator()(Args &&...args) const {
return call(std::forward<Args>(args)...);
}
};
template <>
struct argument_mapper<0> : base_mapper {
template <
typename Arg,
typename ...Args>
static constexpr decltype(auto) call(Arg &&arg, Args &&...args) {
return std::forward<Arg>(arg);
}
template <
typename Arg,
typename ...Args>
constexpr auto operator()(Arg &&arg, Args &&...args) const {
return std::forward<Arg>(arg);
}
};
template <typename Type>
class value_mapper : public base_mapper {
public:
template <typename OtherType>
constexpr value_mapper(OtherType &&value)
: _value(std::forward<OtherType>(value)) {
}
template <typename ...Args>
constexpr auto operator()(Args &&...args) const {
return _value;
}
private:
Type _value;
};
template <typename Type>
struct wrap_mapper {
using type = std::conditional_t<
is_mapper_v<Type>,
Type,
value_mapper<Type>>;
};
template <typename Type>
using wrap_mapper_t = typename wrap_mapper<Type>::type;
template <typename Type, typename Operator>
class unary_operator_mapper : public base_mapper {
using TypeWrapper = wrap_mapper_t<std::decay_t<Type>>;
public:
template <typename OtherType>
constexpr unary_operator_mapper(OtherType &&value)
: _value(std::forward<OtherType>(value)) {
}
template <
typename ...Args,
typename Result = decltype((Operator{})(
std::declval<TypeWrapper>()(std::declval<Args>()...)))>
constexpr std::decay_t<Result> operator()(Args &&...args) const {
return (Operator{})(
_value(std::forward<Args>(args)...));
}
private:
TypeWrapper _value;
};
template <typename Left, typename Right, typename Operator>
class binary_operator_mapper : public base_mapper {
using LeftWrapper = wrap_mapper_t<std::decay_t<Left>>;
using RightWrapper = wrap_mapper_t<std::decay_t<Right>>;
public:
template <typename OtherLeft, typename OtherRight>
constexpr binary_operator_mapper(OtherLeft &&left, OtherRight &&right)
: _left(std::forward<OtherLeft>(left))
, _right(std::forward<OtherRight>(right)) {
}
template <
typename ...Args,
typename Result = decltype((Operator{})(
std::declval<LeftWrapper>()(std::declval<Args>()...),
std::declval<RightWrapper>()(std::declval<Args>()...)))>
constexpr std::decay_t<Result> operator()(Args &&...args) const {
return (Operator{})(
_left(std::forward<Args>(args)...),
_right(std::forward<Args>(args)...));
}
private:
LeftWrapper _left;
RightWrapper _right;
};
template <
typename Left,
typename Right,
typename = std::enable_if_t<
is_mapper_v<Left> || is_mapper_v<Right>
>>
inline auto operator+(Left &&left, Right &&right) {
return binary_operator_mapper<
Left,
Right,
std::plus<>>(
std::forward<Left>(left),
std::forward<Right>(right));
}
template <
typename Left,
typename Right,
typename = std::enable_if_t<
is_mapper_v<Left> || is_mapper_v<Right>
>>
inline auto operator-(Left &&left, Right &&right) {
return binary_operator_mapper<
Left,
Right,
std::minus<>>(
std::forward<Left>(left),
std::forward<Right>(right));
}
template <
typename Left,
typename Right,
typename = std::enable_if_t<
is_mapper_v<Left> || is_mapper_v<Right>
>>
inline auto operator*(Left &&left, Right &&right) {
return binary_operator_mapper<
Left,
Right,
std::multiplies<>>(
std::forward<Left>(left),
std::forward<Right>(right));
}
template <
typename Left,
typename Right,
typename = std::enable_if_t<
is_mapper_v<Left> || is_mapper_v<Right>
>>
inline auto operator/(Left &&left, Right &&right) {
return binary_operator_mapper<
Left,
Right,
std::divides<>>(
std::forward<Left>(left),
std::forward<Right>(right));
}
template <
typename Left,
typename Right,
typename = std::enable_if_t<
is_mapper_v<Left> || is_mapper_v<Right>
>>
inline auto operator%(Left &&left, Right &&right) {
return binary_operator_mapper<
Left,
Right,
std::modulus<>>(
std::forward<Left>(left),
std::forward<Right>(right));
}
template <
typename Type,
typename = std::enable_if_t<
is_mapper_v<Type>
>>
inline auto operator-(Type &&value) {
return unary_operator_mapper<
Type,
std::negate<>>(
std::forward<Type>(value));
}
template <
typename Left,
typename Right,
typename = std::enable_if_t<
is_mapper_v<Left> || is_mapper_v<Right>
>>
inline auto operator<(Left &&left, Right &&right) {
return binary_operator_mapper<
Left,
Right,
std::less<>>(
std::forward<Left>(left),
std::forward<Right>(right));
}
template <
typename Left,
typename Right,
typename = std::enable_if_t<
is_mapper_v<Left> || is_mapper_v<Right>
>>
inline auto operator<=(Left &&left, Right &&right) {
return binary_operator_mapper<
Left,
Right,
std::less_equal<>>(
std::forward<Left>(left),
std::forward<Right>(right));
}
template <
typename Left,
typename Right,
typename = std::enable_if_t<
is_mapper_v<Left> || is_mapper_v<Right>
>>
inline auto operator>(Left &&left, Right &&right) {
return binary_operator_mapper<
Left,
Right,
std::greater<>>(
std::forward<Left>(left),
std::forward<Right>(right));
}
template <
typename Left,
typename Right,
typename = std::enable_if_t<
is_mapper_v<Left> || is_mapper_v<Right>
>>
inline auto operator>=(Left &&left, Right &&right) {
return binary_operator_mapper<
Left,
Right,
std::greater_equal<>>(
std::forward<Left>(left),
std::forward<Right>(right));
}
template <
typename Left,
typename Right,
typename = std::enable_if_t<
is_mapper_v<Left> || is_mapper_v<Right>
>>
inline auto operator==(Left &&left, Right &&right) {
return binary_operator_mapper<
Left,
Right,
std::equal_to<>>(
std::forward<Left>(left),
std::forward<Right>(right));
}
template <
typename Left,
typename Right,
typename = std::enable_if_t<
is_mapper_v<Left> || is_mapper_v<Right>
>>
inline auto operator!=(Left &&left, Right &&right) {
return binary_operator_mapper<
Left,
Right,
std::not_equal_to<>>(
std::forward<Left>(left),
std::forward<Right>(right));
}
template <
typename Left,
typename Right,
typename = std::enable_if_t<
is_mapper_v<Left> || is_mapper_v<Right>
>>
inline auto operator&&(Left &&left, Right &&right) {
return binary_operator_mapper<
Left,
Right,
std::logical_and<>>(
std::forward<Left>(left),
std::forward<Right>(right));
}
template <
typename Left,
typename Right,
typename = std::enable_if_t<
is_mapper_v<Left> || is_mapper_v<Right>
>>
inline auto operator||(Left &&left, Right &&right) {
return binary_operator_mapper<
Left,
Right,
std::logical_or<>>(
std::forward<Left>(left),
std::forward<Right>(right));
}
template <
typename Type,
typename = std::enable_if_t<
is_mapper_v<Type>
>>
inline auto operator!(Type &&value) {
return unary_operator_mapper<
Type,
std::logical_not<>>(
std::forward<Type>(value));
}
template <
typename Left,
typename Right,
typename = std::enable_if_t<
is_mapper_v<Left> || is_mapper_v<Right>
>>
inline auto operator&(Left &&left, Right &&right) {
return binary_operator_mapper<
Left,
Right,
std::bit_and<>>(
std::forward<Left>(left),
std::forward<Right>(right));
}
template <
typename Left,
typename Right,
typename = std::enable_if_t<
is_mapper_v<Left> || is_mapper_v<Right>
>>
inline auto operator|(Left &&left, Right &&right) {
return binary_operator_mapper<
Left,
Right,
std::bit_or<>>(
std::forward<Left>(left),
std::forward<Right>(right));
}
template <
typename Left,
typename Right,
typename = std::enable_if_t<
is_mapper_v<Left> || is_mapper_v<Right>
>>
inline auto operator^(Left &&left, Right &&right) {
return binary_operator_mapper<
Left,
Right,
std::bit_xor<>>(
std::forward<Left>(left),
std::forward<Right>(right));
}
template <
typename Type,
typename = std::enable_if_t<
is_mapper_v<Type>
>>
inline auto operator~(Type &&value) {
return unary_operator_mapper<
Type,
std::bit_not<>>(
std::forward<Type>(value));
}
template <typename ...Mappers>
class tuple_mapper {
template <typename ...Args>
using tuple_result = std::tuple<decltype(
std::declval<wrap_mapper_t<std::decay_t<Mappers>>>()(
std::declval<Args>()...))...>;
public:
template <typename ...OtherMappers>
tuple_mapper(OtherMappers &&...mappers) : _mappers(
std::forward<OtherMappers>(mappers)...) {
}
template <typename ...Args>
constexpr tuple_result<Args...> operator()(
Args &&...args) const {
constexpr auto kArity = sizeof...(Mappers);
return call_helper(
std::make_index_sequence<kArity>(),
std::forward<Args>(args)...);
}
private:
template <typename ...Args, std::size_t ...I>
inline tuple_result<Args...> call_helper(
std::index_sequence<I...>,
Args &&...args) const {
return std::make_tuple(
std::get<I>(_mappers)(std::forward<Args>(args)...)...);
}
std::tuple<wrap_mapper_t<std::decay_t<Mappers>>...> _mappers;
};
template <typename ...Args>
tuple_mapper<Args...> tuple(Args &&...args) {
return tuple_mapper<Args...>(std::forward<Args>(args)...);
}
} // namespace details
namespace mappers {
constexpr const details::argument_mapper<0> _1;
constexpr const details::argument_mapper<1> _2;
constexpr const details::argument_mapper<2> _3;
constexpr const details::argument_mapper<3> _4;
constexpr const details::argument_mapper<4> _5;
constexpr const details::argument_mapper<5> _6;
constexpr const details::argument_mapper<6> _7;
constexpr const details::argument_mapper<7> _8;
constexpr const details::argument_mapper<8> _9;
constexpr const details::argument_mapper<9> _10;
constexpr const auto _1_of_two = ((void)_2, _1);
} // namespace mappers
} // namespace rpl

View File

@@ -0,0 +1,148 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <rpl/producer.h>
namespace rpl {
namespace details {
struct merge_state {
merge_state(int working) : working(working) {
}
int working = 0;
};
template <size_t Index, typename consumer_type>
class merge_subscribe_one {
public:
merge_subscribe_one(
const consumer_type &consumer,
merge_state *state)
: _consumer(consumer)
, _state(state) {
}
template <typename Value, typename Error, typename Generator>
void subscribe(producer<Value, Error, Generator> &&producer) {
_consumer.add_lifetime(std::move(producer).start(
[consumer = _consumer](auto &&value) {
consumer.put_next_forward(
std::forward<decltype(value)>(value));
}, [consumer = _consumer](auto &&error) {
consumer.put_error_forward(
std::forward<decltype(error)>(error));
}, [consumer = _consumer, state = _state] {
if (!--state->working) {
consumer.put_done();
}
}));
}
private:
const consumer_type &_consumer;
merge_state *_state = nullptr;
};
template <
typename consumer_type,
typename Value,
typename Error,
typename ...Generators,
std::size_t ...I>
inline void merge_subscribe(
const consumer_type &consumer,
merge_state *state,
std::index_sequence<I...>,
std::tuple<producer<Value, Error, Generators>...> &&saved) {
auto consume = { (
details::merge_subscribe_one<I, consumer_type>(
consumer,
state
).subscribe(std::get<I>(std::move(saved))), 0)... };
(void)consume;
}
template <typename ...Producers>
class merge_implementation_helper;
template <typename ...Producers>
merge_implementation_helper<std::decay_t<Producers>...>
make_merge_implementation_helper(Producers &&...producers) {
return merge_implementation_helper<std::decay_t<Producers>...>(
std::forward<Producers>(producers)...);
}
template <
typename Value,
typename Error,
typename ...Generators>
class merge_implementation_helper<producer<Value, Error, Generators>...> {
public:
merge_implementation_helper(
producer<Value, Error, Generators> &&...producers)
: _saved(std::make_tuple(std::move(producers)...)) {
}
template <typename Handlers>
lifetime operator()(const consumer<Value, Error, Handlers> &consumer) {
auto state = consumer.template make_state<
details::merge_state>(sizeof...(Generators));
constexpr auto kArity = sizeof...(Generators);
details::merge_subscribe(
consumer,
state,
std::make_index_sequence<kArity>(),
std::move(_saved));
return lifetime();
}
private:
std::tuple<producer<Value, Error, Generators>...> _saved;
};
template <
typename Value,
typename Error,
typename ...Generators>
inline auto merge_implementation(
producer<Value, Error, Generators> &&...producers) {
return make_producer<Value, Error>(
make_merge_implementation_helper(std::move(producers)...));
}
template <typename ...Args>
struct merge_producers : std::false_type {
};
template <typename ...Args>
constexpr bool merge_producers_v
= merge_producers<Args...>::value;
template <
typename Value,
typename Error,
typename ...Generators>
struct merge_producers<
producer<Value, Error, Generators>...>
: std::true_type {
};
} // namespace details
template <
typename ...Args,
typename = std::enable_if_t<
details::merge_producers_v<Args...>>>
inline decltype(auto) merge(Args &&...args) {
return details::merge_implementation(std::move(args)...);
}
} // namespace rpl

View File

@@ -0,0 +1,20 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <rpl/producer.h>
namespace rpl {
template <typename Value = empty_value, typename Error = no_error>
inline auto never() {
return make_producer<Value, Error>([](const auto &consumer) {
return lifetime();
});
}
} // namespace rpl

View File

@@ -0,0 +1,507 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include <catch.hpp>
#include <rpl/rpl.h>
#include <string>
using namespace rpl;
class OnDestructor {
public:
OnDestructor(std::function<void()> callback)
: _callback(std::move(callback)) {
}
~OnDestructor() {
if (_callback) {
_callback();
}
}
private:
std::function<void()> _callback;
};
class InvokeCounter {
public:
InvokeCounter(
const std::shared_ptr<int> &copyCounter,
const std::shared_ptr<int> &moveCounter)
: _copyCounter(copyCounter)
, _moveCounter(moveCounter) {
}
InvokeCounter(const InvokeCounter &other)
: _copyCounter(other._copyCounter)
, _moveCounter(other._moveCounter) {
if (_copyCounter) {
++*_copyCounter;
}
}
InvokeCounter(InvokeCounter &&other)
: _copyCounter(details::take(other._copyCounter))
, _moveCounter(details::take(other._moveCounter)) {
if (_moveCounter) {
++*_moveCounter;
}
}
InvokeCounter &operator=(const InvokeCounter &other) {
_copyCounter = other._copyCounter;
_moveCounter = other._moveCounter;
if (_copyCounter) {
++*_copyCounter;
}
return *this;
}
InvokeCounter &operator=(InvokeCounter &&other) {
_copyCounter = details::take(other._copyCounter);
_moveCounter = details::take(other._moveCounter);
if (_moveCounter) {
++*_moveCounter;
}
return *this;
}
private:
std::shared_ptr<int> _copyCounter;
std::shared_ptr<int> _moveCounter;
};
TEST_CASE("basic operators tests", "[rpl::operators]") {
SECTION("single test") {
auto sum = std::make_shared<int>(0);
auto doneGenerated = std::make_shared<bool>(false);
auto destroyed = std::make_shared<bool>(false);
auto copyCount = std::make_shared<int>(0);
auto moveCount = std::make_shared<int>(0);
{
InvokeCounter counter(copyCount, moveCount);
auto destroyCalled = std::make_shared<OnDestructor>([=] {
*destroyed = true;
});
rpl::lifetime lifetime;
single(std::move(counter))
| on_next_error_done([=](InvokeCounter&&) {
(void)destroyCalled;
++*sum;
}, [=](no_error) {
(void)destroyCalled;
}, [=] {
(void)destroyCalled;
*doneGenerated = true;
}, lifetime);
}
REQUIRE(*sum == 1);
REQUIRE(*doneGenerated);
REQUIRE(*destroyed);
REQUIRE(*copyCount == 0);
}
SECTION("then test") {
auto sum = std::make_shared<int>(0);
auto doneGenerated = std::make_shared<bool>(false);
auto destroyed = std::make_shared<bool>(false);
auto copyCount = std::make_shared<int>(0);
auto moveCount = std::make_shared<int>(0);
{
auto testing = complete<InvokeCounter>() | type_erased();
for (auto i = 0; i != 5; ++i) {
InvokeCounter counter(copyCount, moveCount);
testing = std::move(testing)
| then(single(std::move(counter)));
}
auto destroyCalled = std::make_shared<OnDestructor>([=] {
*destroyed = true;
});
rpl::lifetime lifetime;
std::move(testing)
| then(complete<InvokeCounter>())
| on_next_error_done([=](InvokeCounter&&) {
(void)destroyCalled;
++*sum;
}, [=](no_error) {
(void)destroyCalled;
}, [=] {
(void)destroyCalled;
*doneGenerated = true;
}, lifetime);
}
REQUIRE(*sum == 5);
REQUIRE(*doneGenerated);
REQUIRE(*destroyed);
REQUIRE(*copyCount == 0);
}
SECTION("map test") {
auto sum = std::make_shared<std::string>("");
{
rpl::lifetime lifetime;
single(1)
| then(single(2))
| then(single(3))
| then(single(4))
| then(single(5))
| map([](int value) {
return std::to_string(value);
})
| on_next([=](std::string &&value) {
*sum += std::move(value) + ' ';
}, lifetime);
}
REQUIRE(*sum == "1 2 3 4 5 ");
}
SECTION("deferred test") {
auto launched = std::make_shared<int>(0);
auto checked = std::make_shared<int>(0);
{
rpl::lifetime lifetime;
auto make_next = [=] {
return deferred([=] {
return single(++*launched);
});
};
make_next()
| then(make_next())
| then(make_next())
| then(make_next())
| then(make_next())
| on_next([=](int value) {
REQUIRE(++*checked == *launched);
REQUIRE(*checked == value);
}, lifetime);
REQUIRE(*launched == 5);
}
}
SECTION("filter test") {
auto sum = std::make_shared<std::string>("");
{
rpl::lifetime lifetime;
single(1)
| then(single(1))
| then(single(2))
| then(single(2))
| then(single(3))
| filter([](int value) { return value != 2; })
| map([](int value) {
return std::to_string(value);
})
| on_next([=](std::string &&value) {
*sum += std::move(value) + ' ';
}, lifetime);
}
REQUIRE(*sum == "1 1 3 ");
}
SECTION("filter tuple test") {
auto sum = std::make_shared<std::string>("");
{
auto lifetime = single(std::make_tuple(1, 2))
| then(single(std::make_tuple(1, 2)))
| then(single(std::make_tuple(2, 3)))
| then(single(std::make_tuple(2, 3)))
| then(single(std::make_tuple(3, 4)))
| filter([](auto first, auto second) { return first != 2; })
| map([](auto first, auto second) {
return std::to_string(second);
})
| on_next([=](std::string &&value) {
*sum += std::move(value) + ' ';
});
}
REQUIRE(*sum == "2 2 4 ");
}
SECTION("distinct_until_changed test") {
auto sum = std::make_shared<std::string>("");
{
rpl::lifetime lifetime;
single(1)
| then(single(1))
| then(single(2))
| then(single(2))
| then(single(3))
| distinct_until_changed()
| map([](int value) {
return std::to_string(value);
})
| on_next([=](std::string &&value) {
*sum += std::move(value) + ' ';
}, lifetime);
}
REQUIRE(*sum == "1 2 3 ");
}
SECTION("flatten_latest test") {
auto sum = std::make_shared<std::string>("");
{
rpl::lifetime lifetime;
{
event_stream<int> stream;
single(single(1) | then(single(2)))
| then(single(single(3) | then(single(4))))
| then(single(single(5) | then(stream.events())))
| flatten_latest()
| map([](int value) {
return std::to_string(value);
})
| on_next_done([=](std::string &&value) {
*sum += std::move(value) + ' ';
}, [=] {
*sum += "done ";
}, lifetime);
stream.fire(6);
}
single(single(1))
| then(single(single(2) | then(single(3))))
| then(single(single(4) | then(single(5)) | then(single(6))))
| flatten_latest()
| map([](int value) {
return std::to_string(value);
})
| on_next([=](std::string &&value) {
*sum += std::move(value) + ' ';
}, lifetime);
}
REQUIRE(*sum == "1 2 3 4 5 6 done 1 2 3 4 5 6 ");
}
SECTION("combine vector test") {
auto sum = std::make_shared<std::string>("");
{
rpl::lifetime lifetime;
event_stream<bool> a;
event_stream<bool> b;
event_stream<bool> c;
std::vector<producer<bool>> v;
v.push_back(a.events());
v.push_back(b.events());
v.push_back(c.events());
combine(std::move(v), [](const auto &values) {
return values[0] && values[1] && !values[2];
})
| on_next([=](bool value) {
*sum += std::to_string(value ? 1 : 0);
}, lifetime);
a.fire(true);
b.fire(true);
c.fire(false);
a.fire(false);
b.fire(true);
a.fire(true);
c.fire(true);
}
REQUIRE(*sum == "10010");
}
SECTION("combine test") {
auto sum = std::make_shared<std::string>("");
{
rpl::lifetime lifetime;
event_stream<int> a;
event_stream<short> b;
event_stream<char> c;
combine(
a.events(),
b.events(),
c.events(),
[](long a, long b, long c) {
return a;
})
| on_next([=](int value) {
*sum += std::to_string(value);
}, lifetime);
combine(
a.events(),
b.events(),
c.events(),
[](auto &&value) {
return std::get<1>(value);
})
| on_next([=](int value) {
*sum += std::to_string(value);
}, lifetime);
combine(a.events(), b.events(), c.events())
| map([](auto &&value) {
return std::make_tuple(
std::to_string(std::get<0>(value)),
std::to_string(std::get<1>(value)),
std::to_string(std::get<2>(value)));
})
| on_next([=](auto &&value) {
*sum += std::get<0>(value) + ' '
+ std::get<1>(value) + ' '
+ std::get<2>(value) + ' ';
}, lifetime);
a.fire(1);
b.fire(2);
c.fire(3);
a.fire(4);
b.fire(5);
c.fire(6);
}
REQUIRE(*sum == "121 2 3 424 2 3 454 5 3 454 5 6 ");
}
SECTION("mappers test") {
auto sum = std::make_shared<std::string>("");
{
rpl::lifetime lifetime;
event_stream<int> a;
event_stream<short> b;
event_stream<char> c;
using namespace mappers;
// MSVC BUG + REGRESSION rpl::mappers::tuple :(
//combine(
// a.events(),
// b.events(),
// tuple(_1, _1 + _2)
//) | rpl::on_next([=](int a, int a_plus_b) {
// *sum += std::to_string(a * a_plus_b);
//}, lifetime);
combine(
a.events(),
b.events(),
c.events(),
_1 + _2 + _3 + 10)
| on_next([=](int value) {
*sum += std::to_string(value);
}, lifetime);
a.fire(1);
b.fire(2);
c.fire(3);
a.fire(4);
b.fire(5);
c.fire(6);
}
REQUIRE(*sum == "16192225");
// MSVC BUG + REGRESSION rpl::mappers::tuple :(
//REQUIRE(*sum == "3162419362225");
}
SECTION("after_next test") {
auto sum = std::make_shared<std::string>("");
{
rpl::lifetime lifetime;
ints(3)
| after_next([=](int value) {
*sum += std::to_string(-value-1);
})
| on_next([=](int value) {
*sum += std::to_string(value);
}, lifetime);
}
REQUIRE(*sum == "0-11-22-3");
}
SECTION("combine_previous test") {
auto sum = std::make_shared<std::string>("");
{
rpl::lifetime lifetime;
event_stream<int> a;
a.events(
) | combine_previous(
) | on_next([=](int previous, int next) {
*sum += std::to_string(previous) + ' ';
*sum += std::to_string(next) + ' ';
}, lifetime);
a.events(
) | combine_previous(
5
) | on_next([=](int previous, int next) {
*sum += std::to_string(10 + previous) + ' ';
*sum += std::to_string(next) + ' ';
}, lifetime);
a.fire(1);
a.fire(2);
a.fire(3);
a.fire(4);
}
REQUIRE(*sum == "15 1 1 2 11 2 2 3 12 3 3 4 13 4 ");
}
SECTION("take test") {
auto sum = std::make_shared<std::string>("");
{
rpl::lifetime lifetime;
ints(10) | take(3)
| on_next_done([=](int value) {
*sum += std::to_string(value);
}, [=] {
*sum += "done";
}, lifetime);
}
{
rpl::lifetime lifetime;
ints(3) | take(3)
| on_next_done([=](int value) {
*sum += std::to_string(value);
}, [=] {
*sum += "done";
}, lifetime);
}
{
rpl::lifetime lifetime;
ints(3) | take(10)
| on_next_done([=](int value) {
*sum += std::to_string(value);
}, [=] {
*sum += "done";
}, lifetime);
}
REQUIRE(*sum == "012done012done012done");
}
SECTION("skip test") {
auto sum = std::make_shared<std::string>("");
{
rpl::lifetime lifetime;
ints(10) | skip(5)
| on_next_done([=](int value) {
*sum += std::to_string(value);
}, [=] {
*sum += "done";
}, lifetime);
}
{
rpl::lifetime lifetime;
ints(3) | skip(3)
| on_next_done([=](int value) {
*sum += std::to_string(value);
}, [=] {
*sum += "done";
}, lifetime);
}
{
rpl::lifetime lifetime;
ints(3) | skip(10)
| on_next_done([=](int value) {
*sum += std::to_string(value);
}, [=] {
*sum += "done";
}, lifetime);
}
REQUIRE(*sum == "56789donedonedone");
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,440 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include <catch.hpp>
#include <rpl/producer.h>
#include <rpl/event_stream.h>
using namespace rpl;
class OnDestructor {
public:
OnDestructor(std::function<void()> callback)
: _callback(std::move(callback)) {
}
~OnDestructor() {
if (_callback) {
_callback();
}
}
private:
std::function<void()> _callback;
};
TEST_CASE("basic producer tests", "[rpl::producer]") {
SECTION("producer next, done and lifetime end test") {
auto lifetimeEnded = std::make_shared<bool>(false);
auto sum = std::make_shared<int>(0);
auto doneGenerated = std::make_shared<bool>(false);
auto destroyed = std::make_shared<bool>(false);
{
auto destroyCaller = std::make_shared<OnDestructor>([=] {
*destroyed = true;
});
{
auto alive = make_producer<int>([=](auto &&consumer) {
(void)destroyCaller;
consumer.put_next(1);
consumer.put_next(2);
consumer.put_next(3);
consumer.put_done();
return [=] {
(void)destroyCaller;
*lifetimeEnded = true;
};
}).start([=](int value) {
(void)destroyCaller;
*sum += value;
}, [=](no_error) {
(void)destroyCaller;
}, [=]() {
(void)destroyCaller;
*doneGenerated = true;
});
}
}
REQUIRE(*sum == 1 + 2 + 3);
REQUIRE(*doneGenerated);
REQUIRE(*lifetimeEnded);
REQUIRE(*destroyed);
}
SECTION("producer error test") {
auto errorGenerated = std::make_shared<bool>(false);
{
auto alive = make_producer<no_value, bool>([=](auto &&consumer) {
consumer.put_error(true);
return lifetime();
}).start([=](no_value) {
}, [=](bool error) {
*errorGenerated = error;
}, [=]() {
});
}
REQUIRE(*errorGenerated);
}
SECTION("nested lifetimes test") {
auto lifetimeEndCount = std::make_shared<int>(0);
{
auto lifetimes = lifetime();
{
auto testProducer = make_producer<no_value>([=](auto &&consumer) {
return [=] {
++*lifetimeEndCount;
};
});
testProducer.start_copy([=](no_value) {
}, [=](no_error) {
}, [=] {
}, lifetimes);
std::move(testProducer).start([=](no_value) {
}, [=](no_error) {
}, [=] {
}, lifetimes);
}
REQUIRE(*lifetimeEndCount == 0);
}
REQUIRE(*lifetimeEndCount == 2);
}
SECTION("nested producers test") {
auto sum = std::make_shared<int>(0);
auto lifetimeEndCount = std::make_shared<int>(0);
auto saved = lifetime();
{
make_producer<int>([=](auto &&consumer) {
auto inner = make_producer<int>([=](auto &&consumer) {
consumer.put_next(1);
consumer.put_next(2);
consumer.put_next(3);
return [=] {
++*lifetimeEndCount;
};
});
auto result = lifetime([=] {
++*lifetimeEndCount;
});
inner.start_copy([=](int value) {
consumer.put_next_copy(value);
}, [=](no_error) {
}, [=] {
}, result);
std::move(inner).start([=](int value) {
consumer.put_next_copy(value);
}, [=](no_error) {
}, [=] {
}, result);
return result;
}).start([=](int value) {
*sum += value;
}, [=](no_error) {
}, [=] {
}, saved);
}
REQUIRE(*sum == 1 + 2 + 3 + 1 + 2 + 3);
REQUIRE(*lifetimeEndCount == 0);
saved.destroy();
REQUIRE(*lifetimeEndCount == 3);
}
SECTION("tuple producer test") {
auto result = std::make_shared<int>(0);
{
auto alive = make_producer<std::tuple<int, double>>([=](
auto &&consumer) {
consumer.put_next(std::make_tuple(1, 2.));
return lifetime();
}).start([=](int a, double b) {
*result = a + int(b);
}, [=](no_error error) {
}, [=]() {
});
}
REQUIRE(*result == 3);
}
}
TEST_CASE("basic event_streams tests", "[rpl::event_stream]") {
SECTION("event_stream basic test") {
auto sum = std::make_shared<int>(0);
event_stream<int> stream;
stream.fire(1);
stream.fire(2);
stream.fire(3);
{
auto saved = lifetime();
stream.events().start([=](int value) {
*sum += value;
}, [=](no_error) {
}, [=] {
}, saved);
stream.fire(11);
stream.fire(12);
stream.fire(13);
}
stream.fire(21);
stream.fire(22);
stream.fire(23);
REQUIRE(11 + 12 + 13);
}
SECTION("event_stream add in handler test") {
auto sum = std::make_shared<int>(0);
event_stream<int> stream;
{
auto composite = lifetime();
stream.events().start([=, &stream, &composite](int value) {
*sum += value;
stream.events().start([=](int value) {
*sum += value;
}, [=](no_error) {
}, [=] {
}, composite);
}, [=](no_error) {
}, [=] {
}, composite);
{
auto inner = lifetime();
stream.events().start([=, &stream, &inner](int value) {
*sum += value;
stream.events().start([=](int value) {
*sum += value;
}, [=](no_error) {
}, [=] {
}, inner);
}, [=](no_error) {
}, [=] {
}, inner);
stream.fire(1);
stream.fire(2);
stream.fire(3);
}
stream.fire(11);
stream.fire(12);
stream.fire(13);
}
stream.fire(21);
stream.fire(22);
stream.fire(23);
REQUIRE(*sum ==
(1 + 1) +
((2 + 2) + (2 + 2)) +
((3 + 3 + 3) + (3 + 3 + 3)) +
(11 + 11 + 11 + 11) +
(12 + 12 + 12 + 12 + 12) +
(13 + 13 + 13 + 13 + 13 + 13));
}
SECTION("event_stream add and remove in handler test") {
auto sum = std::make_shared<int>(0);
event_stream<int> stream;
{
auto composite = lifetime();
stream.events().start([=, &stream, &composite](int value) {
*sum += value;
composite.destroy();
stream.events().start([=](int value) {
*sum += value;
}, [=](no_error) {
}, [=] {
}, composite);
}, [=](no_error) {
}, [=] {
}, composite);
{
auto inner = lifetime();
stream.events().start([=, &stream, &inner](int value) {
*sum += value;
inner.destroy();
stream.events().start([=](int value) {
*sum += value;
}, [=](no_error) {
}, [=] {
}, inner);
}, [=](no_error) {
}, [=] {
}, inner);
stream.fire(1);
stream.fire(2);
stream.fire(3);
}
stream.fire(11);
stream.fire(12);
stream.fire(13);
}
stream.fire(21);
stream.fire(22);
stream.fire(23);
REQUIRE(*sum ==
(1 + 1) +
(2 + 2) +
(3 + 3) +
(11) +
(12) +
(13));
}
SECTION("event_stream ends before handler lifetime") {
auto sum = std::make_shared<int>(0);
lifetime extended;
{
event_stream<int> stream;
stream.events().start([=](int value) {
*sum += value;
}, [=](no_error) {
}, [=] {
}, extended);
stream.fire(1);
stream.fire(2);
stream.fire(3);
}
REQUIRE(*sum == 1 + 2 + 3);
}
SECTION("event_stream move test") {
auto sum = std::make_shared<int>(0);
lifetime extended;
{
event_stream<int> stream;
stream.events()
| on_next([=](int value) {
*sum += value;
}, extended);
stream.fire(1);
stream.fire(2);
auto movedStream = std::move(stream);
movedStream.fire(3);
movedStream.fire(4);
}
REQUIRE(*sum == 1 + 2 + 3 + 4);
}
}
TEST_CASE("basic piping tests", "[rpl::producer]") {
SECTION("on_*") {
auto sum = std::make_shared<int>(0);
auto dones = std::make_shared<int>(0);
{
auto alive = lifetime();
make_producer<int, int>([=](auto &&consumer) {
consumer.put_next(1);
consumer.put_done();
return lifetime();
}) | on_next([=](int value) {
*sum += value;
}, alive);
make_producer<int, int>([=](auto &&consumer) {
consumer.put_next(11);
consumer.put_error(111);
return lifetime();
}) | on_error([=](int value) {
*sum += value;
}, alive);
make_producer<int, int>([=](auto &&consumer) {
consumer.put_next(1111);
consumer.put_done();
return lifetime();
}) | on_done([=]() {
*dones += 1;
}, alive);
make_producer<int, int>([=](auto &&consumer) {
consumer.put_next(11111);
consumer.put_next(11112);
consumer.put_next(11113);
consumer.put_error(11114);
return lifetime();
}) | on_next_error([=](int value) {
*sum += value;
}, [=](int value) {
*sum += value;
}, alive);
}
auto alive = lifetime();
make_producer<int, int>([=](auto &&consumer) {
consumer.put_next(111111);
consumer.put_next(111112);
consumer.put_next(111113);
consumer.put_done();
return lifetime();
}) | on_next_done([=](int value) {
*sum += value;
}, [=]() {
*dones += 11;
}, alive);
make_producer<int, int>([=](auto &&consumer) {
consumer.put_error(1111111);
return lifetime();
}) | on_error_done([=](int value) {
*sum += value;
}, [=]() {
*dones = 0;
}, alive);
make_producer<int, int>([=](auto &&consumer) {
consumer.put_next(11111111);
consumer.put_next(11111112);
consumer.put_next(11111113);
consumer.put_error(11111114);
return lifetime();
}) | on_next_error_done([=](int value) {
*sum += value;
}, [=](int value) {
*sum += value;
}, [=]() {
*dones = 0;
}, alive);
REQUIRE(*sum ==
1 +
111 +
11111 + 11112 + 11113 + 11114 +
111111 + 111112 + 111113 +
1111111 +
11111111 + 11111112 + 11111113 + 11111114);
REQUIRE(*dones == 1 + 11);
}
SECTION("on_next should copy its callback") {
auto sum = std::make_shared<int>(0);
{
auto next = [=](int value) {
REQUIRE(sum != nullptr);
*sum += value;
};
for (int i = 0; i != 3; ++i) {
auto alive = lifetime();
make_producer<int, int>([=](auto &&consumer) {
consumer.put_next(1);
consumer.put_done();
return lifetime();
}) | on_next(next, alive);
}
}
REQUIRE(*sum == 3);
}
}

View File

@@ -0,0 +1,86 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <rpl/producer.h>
#include <vector>
namespace rpl {
template <typename Value, typename Error = no_error>
inline auto single(Value &&value) {
return make_producer<std::decay_t<Value>, Error>([
value = std::forward<Value>(value)
](const auto &consumer) mutable {
consumer.put_next(std::move(value));
consumer.put_done();
return lifetime();
});
}
template <typename Error = no_error>
inline auto single() {
return make_producer<rpl::empty_value, Error>([](const auto &consumer) {
consumer.put_next({});
consumer.put_done();
return lifetime();
});
}
template <typename Value, typename Error = no_error>
inline auto vector(std::vector<Value> &&values) {
return make_producer<Value, Error>([
values = std::move(values)
](const auto &consumer) mutable {
for (auto &value : values) {
consumer.put_next(std::move(value));
}
consumer.put_done();
return lifetime();
});
}
template <typename Error = no_error>
inline auto vector(std::vector<bool> &&values) {
return make_producer<bool, Error>([
values = std::move(values)
](const auto &consumer) {
for (auto value : values) {
consumer.put_next_copy(value);
}
consumer.put_done();
return lifetime();
});
}
template <
typename Range,
typename Value = std::decay_t<
decltype(*std::begin(std::declval<Range>()))>>
inline auto range(Range &&range) {
return vector(std::vector<Value>(
std::begin(range),
std::end(range)));
}
inline auto ints(int from, int till) {
Expects(from <= till);
return make_producer<int>([from, till](const auto &consumer) {
for (auto i = from; i != till; ++i) {
consumer.put_next_copy(i);
}
consumer.put_done();
return lifetime();
});
}
inline auto ints(int count) {
return ints(0, count);
}
} // namespace rpl

View File

@@ -0,0 +1,38 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
// rpl - reactive programming library
#include <rpl/lifetime.h>
#include <rpl/consumer.h>
#include <rpl/producer.h>
#include <rpl/event_stream.h>
#include <rpl/range.h>
#include <rpl/complete.h>
#include <rpl/fail.h>
#include <rpl/never.h>
#include <rpl/take.h>
#include <rpl/skip.h>
#include <rpl/then.h>
#include <rpl/deferred.h>
#include <rpl/map.h>
#include <rpl/mappers.h>
#include <rpl/merge.h>
#include <rpl/filter.h>
#include <rpl/distinct_until_changed.h>
#include <rpl/type_erased.h>
#include <rpl/flatten_latest.h>
#include <rpl/combine.h>
#include <rpl/combine_previous.h>
#include <rpl/conditional.h>
#include <rpl/variable.h>
#include <rpl/before_next.h>
#include <rpl/after_next.h>

View File

@@ -0,0 +1,60 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
namespace rpl {
namespace details {
class skip_helper {
public:
skip_helper(int count) : _count(count) {
}
template <
typename Value,
typename Error,
typename Generator>
auto operator()(producer<Value, Error, Generator> &&initial) {
return make_producer<Value, Error>([
initial = std::move(initial),
skipping = _count
](const auto &consumer) mutable {
auto count = consumer.template make_state<int>(skipping);
auto initial_consumer = make_consumer<Value, Error>(
[consumer, count](auto &&value) {
if (*count) {
--*count;
} else {
consumer.put_next_forward(
std::forward<decltype(value)>(value));
}
}, [consumer](auto &&error) {
consumer.put_error_forward(
std::forward<decltype(error)>(error));
}, [consumer] {
consumer.put_done();
});
consumer.add_lifetime(initial_consumer.terminator());
return std::move(initial).start_existing(initial_consumer);
});
}
private:
int _count = 0;
};
} // namespace details
inline auto skip(int count)
-> details::skip_helper {
Expects(count >= 0);
return details::skip_helper(count);
}
} // namespace rpl

122
Telegram/lib_rpl/rpl/take.h Normal file
View File

@@ -0,0 +1,122 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
namespace rpl {
namespace details {
class take_helper {
public:
take_helper(int count) : _count(count) {
}
template <
typename Value,
typename Error,
typename Generator>
auto operator()(producer<Value, Error, Generator> &&initial) {
return make_producer<Value, Error>([
initial = std::move(initial),
limit = _count
](const auto &consumer) mutable {
auto count = consumer.template make_state<int>(limit);
auto initial_consumer = make_consumer<Value, Error>(
[consumer, count](auto &&value) {
auto left = (*count)--;
if (left) {
consumer.put_next_forward(
std::forward<decltype(value)>(value));
--left;
}
if (!left) {
consumer.put_done();
}
}, [consumer](auto &&error) {
consumer.put_error_forward(
std::forward<decltype(error)>(error));
}, [consumer] {
consumer.put_done();
});
consumer.add_lifetime(initial_consumer.terminator());
return std::move(initial).start_existing(initial_consumer);
});
}
private:
int _count = 0;
};
} // namespace details
inline auto take(int count)
-> details::take_helper {
Expects(count >= 0);
return details::take_helper(count);
}
namespace details {
template <typename Predicate>
class take_while_helper {
public:
template <typename OtherPredicate>
take_while_helper(OtherPredicate &&predicate)
: _predicate(std::forward<OtherPredicate>(predicate)) {
}
template <
typename Value,
typename Error,
typename Generator,
typename = std::enable_if_t<
details::is_callable_v<Predicate, Value>>>
auto operator()(producer<Value, Error, Generator> &&initial) {
return make_producer<Value, Error>([
initial = std::move(initial),
predicate = std::move(_predicate)
](const auto &consumer) mutable {
return std::move(initial).start(
[
consumer,
predicate = std::move(predicate)
](auto &&value) {
const auto &immutable = value;
if (details::callable_invoke(
predicate,
immutable)
) {
consumer.put_next_forward(
std::forward<decltype(value)>(value));
} else {
consumer.put_done();
}
}, [consumer](auto &&error) {
consumer.put_error_forward(
std::forward<decltype(error)>(error));
}, [consumer] {
consumer.put_done();
});
});
}
private:
Predicate _predicate;
};
} // namespace details
template <typename Predicate>
inline auto take_while(Predicate &&predicate)
-> details::take_while_helper<std::decay_t<Predicate>> {
return details::take_while_helper<std::decay_t<Predicate>>(
std::forward<Predicate>(predicate));
}
} // namespace rpl

View File

@@ -0,0 +1,71 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <rpl/producer.h>
namespace rpl {
namespace details {
template <typename Value, typename Error, typename Generator>
class then_helper {
public:
then_helper(producer<Value, Error, Generator> &&following)
: _following(std::move(following)) {
}
template <
typename OtherValue,
typename OtherError,
typename OtherGenerator,
typename NewValue = superset_type_t<Value, OtherValue>,
typename NewError = superset_type_t<Error, OtherError>>
auto operator()(
producer<OtherValue, OtherError, OtherGenerator> &&initial
) {
return make_producer<NewValue, NewError>([
initial = std::move(initial),
following = std::move(_following)
](const auto &consumer) mutable {
return std::move(initial).start(
[consumer](auto &&value) {
consumer.put_next_forward(
std::forward<decltype(value)>(value));
}, [consumer](auto &&error) {
consumer.put_error_forward(
std::forward<decltype(error)>(error));
}, [
consumer,
following = std::move(following)
]() mutable {
consumer.add_lifetime(std::move(following).start(
[consumer](auto &&value) {
consumer.put_next_forward(
std::forward<decltype(value)>(value));
}, [consumer](auto &&error) {
consumer.put_error_forward(
std::forward<decltype(error)>(error));
}, [consumer] {
consumer.put_done();
}));
});
});
}
private:
producer<Value, Error, Generator> _following;
};
} // namespace details
template <typename Value, typename Error, typename Generator>
inline auto then(producer<Value, Error, Generator> &&following)
-> details::then_helper<Value, Error, Generator> {
return { std::move(following) };
}
} // namespace rpl

View File

@@ -0,0 +1,26 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <rpl/producer.h>
namespace rpl {
namespace details {
struct type_erased_t {
template <typename Value, typename Error, typename Generator>
producer<Value, Error> operator()(
producer<Value, Error, Generator> &&initial) const {
return std::move(initial);
}
};
} // namespace details
inline constexpr details::type_erased_t type_erased{};
} // namespace rpl

View File

@@ -0,0 +1,167 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <rpl/producer.h>
#include <rpl/event_stream.h>
namespace rpl {
namespace details {
template <typename A, typename B>
struct supports_equality_compare {
template <typename U, typename V>
static auto test(const U *u, const V *v)
-> decltype(*u == *v, true_t());
static false_t test(...);
static constexpr bool value
= (sizeof(test((const A*)nullptr, (const B*)nullptr))
== sizeof(true_t));
};
template <typename A, typename B>
constexpr bool supports_equality_compare_v
= supports_equality_compare<std::decay_t<A>, std::decay_t<B>>::value;
} // namespace details
template <typename Type, typename Error = no_error>
class variable final {
public:
variable() : _data{} {
}
variable(variable &&other) : _data(std::move(other._data)) {
}
variable &operator=(variable &&other) {
return (*this = std::move(other._data));
}
variable(const variable &other) : _data(other._data) {
}
variable &operator=(const variable &other) {
return (*this = other._data);
}
template <
typename OtherType,
typename = std::enable_if_t<
std::is_constructible_v<Type, OtherType&&>>>
variable(OtherType &&data) : _data(std::forward<OtherType>(data)) {
}
template <
typename OtherType,
typename = std::enable_if_t<
std::is_assignable_v<Type&, OtherType&&>>>
variable &operator=(OtherType &&data) {
_lifetime.destroy();
return assign(std::forward<OtherType>(data));
}
template <
typename OtherType,
typename = std::enable_if_t<
std::is_assignable_v<Type&, OtherType&&>>>
void force_assign(OtherType &&data) {
_lifetime.destroy();
_data = std::forward<OtherType>(data);
_changes.fire_copy(_data);
}
template <
typename OtherType,
typename OtherError,
typename Generator,
typename = std::enable_if_t<
std::is_assignable_v<Type&, OtherType>>>
variable(producer<OtherType, OtherError, Generator> &&stream) {
std::move(stream)
| on_next([=](auto &&data) {
assign(std::forward<decltype(data)>(data));
}, _lifetime);
}
template <
typename OtherType,
typename OtherError,
typename Generator,
typename = std::enable_if_t<
std::is_assignable_v<Type&, OtherType>>>
variable &operator=(
producer<OtherType, OtherError, Generator> &&stream) {
_lifetime.destroy();
std::move(stream)
| on_next([=](auto &&data) {
assign(std::forward<decltype(data)>(data));
}, _lifetime);
return *this;
}
std::conditional_t<
(std::is_trivially_copyable_v<Type> && sizeof(Type) <= 16),
Type,
const Type &> current() const {
return _data;
}
auto value() const {
return _changes.events_starting_with_copy(_data);
}
auto changes() const {
return _changes.events();
}
// Send 'done' to all subscribers and unsubscribe them.
template <
typename OtherType,
typename = std::enable_if_t<
std::is_assignable_v<Type&, OtherType>>>
void reset(OtherType &&data) {
_data = std::forward<OtherType>(data);
_changes = event_stream<Type, Error>();
}
void reset() {
reset(Type());
}
template <
typename OtherError,
typename = std::enable_if_t<
std::is_constructible_v<Error, OtherError&&>>>
void reset_with_error(OtherError &&error) {
_changes.fire_error(std::forward<OtherError>(error));
}
void reset_with_error() {
reset_with_error(Error());
}
private:
template <typename OtherType>
variable &assign(OtherType &&data) {
if constexpr (details::supports_equality_compare_v<Type, OtherType>) {
if (!(_data == data)) {
_data = std::forward<OtherType>(data);
_changes.fire_copy(_data);
}
} else if constexpr (details::supports_equality_compare_v<Type, Type>) {
auto old = std::move(_data);
_data = std::forward<OtherType>(data);
if (!(_data == old)) {
_changes.fire_copy(_data);
}
} else {
_data = std::forward<OtherType>(data);
_changes.fire_copy(_data);
}
return *this;
}
Type _data{};
event_stream<Type, Error> _changes;
lifetime _lifetime;
};
} // namespace rpl

View File

@@ -0,0 +1,31 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include <catch.hpp>
#include <rpl/rpl.h>
#include <string>
using namespace rpl;
TEST_CASE("basic variable tests", "[rpl::variable]") {
SECTION("simple test") {
auto sum = std::make_shared<int>(0);
{
auto var = variable<int>(1);
auto lifeftime = var.value()
| on_next([=](int value) {
*sum += value;
});
var = 1;
var = 11;
var = 111;
var = 111;
}
REQUIRE(*sum == 1 + 11 + 111);
}
}