/* This file is part of Telegram Desktop, the official desktop version of Telegram messaging app, see https://telegram.org Telegram Desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #pragma once #include #include #include "base/optional.h" namespace rpl { namespace details { template struct combine_latest_vector_state { std::vector> accumulated; std::vector latest; int invalid = 0; int working = 0; }; } // namespace details template producer, Error> combine_latest( std::vector> &&producers) { if (producers.empty()) { return complete, Error>(); } using state_type = details::combine_latest_vector_state; using consumer_type = consumer, Error>; return [producers = std::move(producers)]( const consumer_type &consumer) mutable { auto count = producers.size(); auto state = consumer.make_state(); 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)); } base::take(state->accumulated); consumer.put_next_copy(state->latest); } } }, [consumer](Error &&error) { consumer.put_error(std::move(error)); }, [consumer, state] { if (!--state->working) { consumer.put_done(); } })); } return lifetime(); }; } namespace details { template struct combine_latest_tuple_state { base::optional first; base::optional> others; int working = 2; }; } // namespace details template < typename Value, typename Error, typename ...Values, typename ...Errors> producer, base::variant> combine_latest( producer &&first, producer &&...others) { auto others_combined = combine_latest(std::move(others)...); return [ first = std::move(first), others = std::move(others_combined) ](const consumer, base::variant> &consumer) mutable { auto state = consumer.make_state>(); consumer.add_lifetime(std::move(first).start([consumer, state](Value &&value) { state->first = std::move(value); if (state->others) { consumer.put_next(std::tuple_cat(std::make_tuple(*state->first), *state->others)); } }, [consumer](Error &&error) { consumer.put_error(std::move(error)); }, [consumer, state] { if (!--state->working) { consumer.put_done(); } })); consumer.add_lifetime(std::move(others).start([consumer, state](std::tuple &&value) { state->others = std::move(value); if (state->first) { consumer.put_next(std::tuple_cat(std::make_tuple(*state->first), *state->others)); } }, [consumer](base::variant &&error) { base::visit([&](auto &&errorValue) { consumer.put_error(std::move(errorValue)); }, std::move(error)); }, [consumer, state] { if (!--state->working) { consumer.put_done(); } })); return lifetime(); }; } template < typename Value, typename Error> producer, Error> combine_latest( producer &&producer) { return std::move(producer) | map([](auto &&value) { return std::make_tuple(std::forward(value)); }); } } // namespace rpl