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
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
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
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
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
|
||||
Reference in New Issue
Block a user