// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. #pragma once #if !defined(RXCPP_OPERATORS_RX_ZIP_HPP) #define RXCPP_OPERATORS_RX_ZIP_HPP #include "../rx-includes.hpp" /*! \file rx-zip.hpp \brief Bring by one item from all given observables and select a value to emit from the new observable that is returned. \tparam AN types of scheduler (optional), aggregate function (optional), and source observables \param an scheduler (optional), aggregation function (optional), and source observables \return Observable that emits the result of combining the items emitted and brought by one from each of the source observables. If scheduler is omitted, identity_current_thread is used. If aggregation function is omitted, the resulting observable returns tuples of emitted items. \sample Neither scheduler nor aggregation function are present: \snippet zip.cpp zip sample \snippet output.txt zip sample Only scheduler is present: \snippet zip.cpp Coordination zip sample \snippet output.txt Coordination zip sample Only aggregation function is present: \snippet zip.cpp Selector zip sample \snippet output.txt Selector zip sample Both scheduler and aggregation function are present: \snippet zip.cpp Coordination+Selector zip sample \snippet output.txt Coordination+Selector zip sample */ namespace rxcpp { namespace operators { namespace detail { template struct zip_source_state { using value_type = rxu::value_type_t; zip_source_state() : completed(false) { } std::list values; bool completed; }; struct values_not_empty { template bool operator()(zip_source_state& source) const { return !source.values.empty(); } }; struct source_completed_values_empty { template bool operator()(zip_source_state& source) const { return source.completed && source.values.empty(); } }; struct extract_value_front { template> Value operator()(zip_source_state& source) const { auto val = std::move(source.values.front()); source.values.pop_front(); return val; } }; template struct zip_invalid_arguments {}; template struct zip_invalid : public rxo::operator_base> { using type = observable, zip_invalid>; }; template using zip_invalid_t = typename zip_invalid::type; template struct is_zip_selector_check { typedef rxu::decay_t selector_type; struct tag_not_valid; template static auto check(int) -> decltype((*(CS*)nullptr)((*(typename CON::value_type*)nullptr)...)); template static tag_not_valid check(...); using type = decltype(check...>(0)); static const bool value = !std::is_same::value; }; template struct invalid_zip_selector { static const bool value = false; }; template struct is_zip_selector : public std::conditional< is_zip_selector_check::value, is_zip_selector_check, invalid_zip_selector>::type { }; template using result_zip_selector_t = typename is_zip_selector::type; template struct zip_traits { typedef std::tuple...> tuple_source_type; typedef std::tuple...> tuple_source_values_type; typedef rxu::decay_t selector_type; typedef rxu::decay_t coordination_type; typedef typename is_zip_selector::type value_type; }; template struct zip : public operator_base>> { typedef zip this_type; typedef zip_traits traits; typedef typename traits::tuple_source_type tuple_source_type; typedef typename traits::tuple_source_values_type tuple_source_values_type; typedef typename traits::selector_type selector_type; typedef typename traits::coordination_type coordination_type; typedef typename coordination_type::coordinator_type coordinator_type; struct values { values(tuple_source_type o, selector_type s, coordination_type sf) : source(std::move(o)) , selector(std::move(s)) , coordination(std::move(sf)) { } tuple_source_type source; selector_type selector; coordination_type coordination; }; values initial; zip(coordination_type sf, selector_type s, tuple_source_type ts) : initial(std::move(ts), std::move(s), std::move(sf)) { } template void subscribe_one(std::shared_ptr state) const { typedef typename std::tuple_element::type::value_type source_value_type; composite_subscription innercs; // when the out observer is unsubscribed all the // inner subscriptions are unsubscribed as well state->out.add(innercs); auto source = on_exception( [&](){return state->coordinator.in(std::get(state->source));}, state->out); if (source.empty()) { return; } // this subscribe does not share the observer subscription // so that when it is unsubscribed the observer can be called // until the inner subscriptions have finished auto sink = make_subscriber( state->out, innercs, // on_next [state](source_value_type st) { auto& values = std::get(state->pending).values; values.push_back(st); if (rxu::apply_to_each(state->pending, values_not_empty(), rxu::all_values_true())) { auto selectedResult = rxu::apply_to_each(state->pending, extract_value_front(), state->selector); state->out.on_next(selectedResult); } if (rxu::apply_to_each(state->pending, source_completed_values_empty(), rxu::any_value_true())) { state->out.on_completed(); } }, // on_error [state](rxu::error_ptr e) { state->out.on_error(e); }, // on_completed [state]() { auto& completed = std::get(state->pending).completed; completed = true; if (--state->pendingCompletions == 0) { state->out.on_completed(); } } ); auto selectedSink = on_exception( [&](){return state->coordinator.out(sink);}, state->out); if (selectedSink.empty()) { return; } source->subscribe(std::move(selectedSink.get())); } template void subscribe_all(std::shared_ptr state, rxu::values) const { bool subscribed[] = {(subscribe_one(state), true)...}; subscribed[0] = (*subscribed); // silence warning } template void on_subscribe(Subscriber scbr) const { static_assert(is_subscriber::value, "subscribe must be passed a subscriber"); typedef Subscriber output_type; struct zip_state_type : public std::enable_shared_from_this , public values { zip_state_type(values i, coordinator_type coor, output_type oarg) : values(std::move(i)) , pendingCompletions(sizeof... (ObservableN)) , valuesSet(0) , coordinator(std::move(coor)) , out(std::move(oarg)) { } // on_completed on the output must wait until all the // subscriptions have received on_completed mutable int pendingCompletions; mutable int valuesSet; mutable tuple_source_values_type pending; coordinator_type coordinator; output_type out; }; auto coordinator = initial.coordination.create_coordinator(scbr.get_subscription()); // take a copy of the values for each subscription auto state = std::make_shared(initial, std::move(coordinator), std::move(scbr)); subscribe_all(state, typename rxu::values_from::type()); } }; } /*! @copydoc rx-zip.hpp */ template auto zip(AN&&... an) -> operator_factory { return operator_factory(std::make_tuple(std::forward(an)...)); } } template<> struct member_overload { template>, class Zip = rxo::detail::zip, rxu::decay_t...>, class Value = rxu::value_type_t, class Result = observable> static Result member(Observable&& o, ObservableN&&... on) { return Result(Zip(identity_current_thread(), rxu::pack(), std::make_tuple(std::forward(o), std::forward(on)...))); } template, all_observables>, class ResolvedSelector = rxu::decay_t, class Zip = rxo::detail::zip, rxu::decay_t...>, class Value = rxu::value_type_t, class Result = observable> static Result member(Observable&& o, Selector&& s, ObservableN&&... on) { return Result(Zip(identity_current_thread(), std::forward(s), std::make_tuple(std::forward(o), std::forward(on)...))); } template, all_observables>, class Zip = rxo::detail::zip, rxu::decay_t...>, class Value = rxu::value_type_t, class Result = observable> static Result member(Observable&& o, Coordination&& cn, ObservableN&&... on) { return Result(Zip(std::forward(cn), rxu::pack(), std::make_tuple(std::forward(o), std::forward(on)...))); } template, operators::detail::is_zip_selector, all_observables>, class ResolvedSelector = rxu::decay_t, class Zip = rxo::detail::zip, rxu::decay_t...>, class Value = rxu::value_type_t, class Result = observable> static Result member(Observable&& o, Coordination&& cn, Selector&& s, ObservableN&&... on) { return Result(Zip(std::forward(cn), std::forward(s), std::make_tuple(std::forward(o), std::forward(on)...))); } template static operators::detail::zip_invalid_t member(const AN&...) { std::terminate(); return {}; static_assert(sizeof...(AN) == 10000, "zip takes (optional Coordination, optional Selector, required Observable, optional Observable...), Selector takes (Observable::value_type...)"); } }; } #endif