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
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:
53
Telegram/lib_rpl/CMakeLists.txt
Normal file
53
Telegram/lib_rpl/CMakeLists.txt
Normal 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
|
||||
)
|
||||
58
Telegram/lib_rpl/rpl/after_next.h
Normal file
58
Telegram/lib_rpl/rpl/after_next.h
Normal 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
|
||||
23
Telegram/lib_rpl/rpl/before_next.h
Normal file
23
Telegram/lib_rpl/rpl/before_next.h
Normal 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
|
||||
348
Telegram/lib_rpl/rpl/combine.h
Normal file
348
Telegram/lib_rpl/rpl/combine.h
Normal 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
|
||||
106
Telegram/lib_rpl/rpl/combine_previous.h
Normal file
106
Telegram/lib_rpl/rpl/combine_previous.h
Normal 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
|
||||
21
Telegram/lib_rpl/rpl/complete.h
Normal file
21
Telegram/lib_rpl/rpl/complete.h
Normal 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
|
||||
51
Telegram/lib_rpl/rpl/conditional.h
Normal file
51
Telegram/lib_rpl/rpl/conditional.h
Normal 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
|
||||
637
Telegram/lib_rpl/rpl/consumer.h
Normal file
637
Telegram/lib_rpl/rpl/consumer.h
Normal 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
|
||||
25
Telegram/lib_rpl/rpl/deferred.h
Normal file
25
Telegram/lib_rpl/rpl/deferred.h
Normal 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
|
||||
146
Telegram/lib_rpl/rpl/details/callable.h
Normal file
146
Telegram/lib_rpl/rpl/details/callable.h
Normal 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
|
||||
22
Telegram/lib_rpl/rpl/details/superset_type.h
Normal file
22
Telegram/lib_rpl/rpl/details/superset_type.h
Normal 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
|
||||
179
Telegram/lib_rpl/rpl/details/type_list.h
Normal file
179
Telegram/lib_rpl/rpl/details/type_list.h
Normal 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
|
||||
49
Telegram/lib_rpl/rpl/distinct_until_changed.h
Normal file
49
Telegram/lib_rpl/rpl/distinct_until_changed.h
Normal 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
|
||||
284
Telegram/lib_rpl/rpl/event_stream.h
Normal file
284
Telegram/lib_rpl/rpl/event_stream.h
Normal 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
|
||||
23
Telegram/lib_rpl/rpl/fail.h
Normal file
23
Telegram/lib_rpl/rpl/fail.h
Normal 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
|
||||
176
Telegram/lib_rpl/rpl/filter.h
Normal file
176
Telegram/lib_rpl/rpl/filter.h
Normal 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
|
||||
73
Telegram/lib_rpl/rpl/flatten_latest.h
Normal file
73
Telegram/lib_rpl/rpl/flatten_latest.h
Normal 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
|
||||
|
||||
95
Telegram/lib_rpl/rpl/lifetime.h
Normal file
95
Telegram/lib_rpl/rpl/lifetime.h
Normal 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
248
Telegram/lib_rpl/rpl/map.h
Normal 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
|
||||
473
Telegram/lib_rpl/rpl/mappers.h
Normal file
473
Telegram/lib_rpl/rpl/mappers.h
Normal 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
|
||||
148
Telegram/lib_rpl/rpl/merge.h
Normal file
148
Telegram/lib_rpl/rpl/merge.h
Normal 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
|
||||
20
Telegram/lib_rpl/rpl/never.h
Normal file
20
Telegram/lib_rpl/rpl/never.h
Normal 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
|
||||
507
Telegram/lib_rpl/rpl/operators_tests.cpp
Normal file
507
Telegram/lib_rpl/rpl/operators_tests.cpp
Normal 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> ©Counter,
|
||||
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");
|
||||
}
|
||||
}
|
||||
1024
Telegram/lib_rpl/rpl/producer.h
Normal file
1024
Telegram/lib_rpl/rpl/producer.h
Normal file
File diff suppressed because it is too large
Load Diff
440
Telegram/lib_rpl/rpl/producer_tests.cpp
Normal file
440
Telegram/lib_rpl/rpl/producer_tests.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
86
Telegram/lib_rpl/rpl/range.h
Normal file
86
Telegram/lib_rpl/rpl/range.h
Normal 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
|
||||
|
||||
38
Telegram/lib_rpl/rpl/rpl.h
Normal file
38
Telegram/lib_rpl/rpl/rpl.h
Normal 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>
|
||||
60
Telegram/lib_rpl/rpl/skip.h
Normal file
60
Telegram/lib_rpl/rpl/skip.h
Normal 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
122
Telegram/lib_rpl/rpl/take.h
Normal 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
|
||||
71
Telegram/lib_rpl/rpl/then.h
Normal file
71
Telegram/lib_rpl/rpl/then.h
Normal 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
|
||||
26
Telegram/lib_rpl/rpl/type_erased.h
Normal file
26
Telegram/lib_rpl/rpl/type_erased.h
Normal 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
|
||||
167
Telegram/lib_rpl/rpl/variable.h
Normal file
167
Telegram/lib_rpl/rpl/variable.h
Normal 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
|
||||
31
Telegram/lib_rpl/rpl/variable_tests.cpp
Normal file
31
Telegram/lib_rpl/rpl/variable_tests.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user