diff --git a/Telegram/SourceFiles/rpl/before_next.h b/Telegram/SourceFiles/rpl/before_next.h new file mode 100644 index 0000000000..f401631ea9 --- /dev/null +++ b/Telegram/SourceFiles/rpl/before_next.h @@ -0,0 +1,37 @@ +/* +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 + +namespace rpl { + +template +auto before_next(SideEffect &&method) { + return filter([method = std::forward(method)]( + const auto &value) { + method(value); + return true; + }); +} + +} // namespace rpl diff --git a/Telegram/SourceFiles/rpl/complete.h b/Telegram/SourceFiles/rpl/complete.h new file mode 100644 index 0000000000..18675590dc --- /dev/null +++ b/Telegram/SourceFiles/rpl/complete.h @@ -0,0 +1,35 @@ +/* +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 + +namespace rpl { + +template +producer complete() { + return [](const consumer &consumer) mutable { + consumer.put_done(); + return lifetime(); + }; +} + +} // namespace rpl diff --git a/Telegram/SourceFiles/rpl/consumer.h b/Telegram/SourceFiles/rpl/consumer.h index 17aa4caef0..38366a5dab 100644 --- a/Telegram/SourceFiles/rpl/consumer.h +++ b/Telegram/SourceFiles/rpl/consumer.h @@ -20,8 +20,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #pragma once -#include "rpl/lifetime.h" #include +#include +#include namespace rpl { @@ -36,7 +37,10 @@ struct no_error { struct empty_value { }; -template +struct empty_error { +}; + +template class consumer { public: template < @@ -57,7 +61,11 @@ public: void put_error_copy(const Error &error) const; void put_done() const; - void set_lifetime(lifetime &&lifetime) const; + void add_lifetime(lifetime &&lifetime) const; + + template + Type *make_state(Args&& ...args) const; + void terminate() const; bool operator==(const consumer &other) const { @@ -103,7 +111,11 @@ public: virtual void put_error(Error &&error) = 0; virtual void put_done() = 0; - void set_lifetime(lifetime &&lifetime); + void add_lifetime(lifetime &&lifetime); + + template + Type *make_state(Args&& ...args); + void terminate(); protected: @@ -210,14 +222,21 @@ void consumer::put_done() const { } template -void consumer::set_lifetime(lifetime &&lifetime) const { +void consumer::add_lifetime(lifetime &&lifetime) const { if (_instance) { - _instance->set_lifetime(std::move(lifetime)); + _instance->add_lifetime(std::move(lifetime)); } else { lifetime.destroy(); } } +template +template +Type *consumer::make_state(Args&& ...args) const { + Expects(_instance != nullptr); + return _instance->template make_state(std::forward(args)...); +} + template void consumer::terminate() const { if (_instance) { @@ -226,7 +245,7 @@ void consumer::terminate() const { } template -void consumer::abstract_consumer_instance::set_lifetime( +void consumer::abstract_consumer_instance::add_lifetime( lifetime &&lifetime) { std::unique_lock lock(_mutex); if (_terminated) { @@ -234,10 +253,19 @@ void consumer::abstract_consumer_instance::set_lifetime( lifetime.destroy(); } else { - _lifetime = std::move(lifetime); + _lifetime.add(std::move(lifetime)); } } +template +template +Type *consumer::abstract_consumer_instance::make_state( + Args&& ...args) { + std::unique_lock lock(_mutex); + Expects(!_terminated); + return _lifetime.template make_state(std::forward(args)...); +} + template void consumer::abstract_consumer_instance::terminate() { std::unique_lock lock(_mutex); diff --git a/Telegram/SourceFiles/rpl/deferred.h b/Telegram/SourceFiles/rpl/deferred.h new file mode 100644 index 0000000000..eefff21629 --- /dev/null +++ b/Telegram/SourceFiles/rpl/deferred.h @@ -0,0 +1,38 @@ +/* +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 + +namespace rpl { + +template < + typename Creator, + typename Value = typename decltype(std::declval()())::value_type, + typename Error = typename decltype(std::declval()())::error_type> +producer deferred(Creator &&creator) { + return [creator = std::forward(creator)]( + const consumer &consumer) mutable { + return std::move(creator)().start_existing(consumer); + }; +} + +} // namespace rpl diff --git a/Telegram/SourceFiles/rpl/distinct_until_changed.h b/Telegram/SourceFiles/rpl/distinct_until_changed.h new file mode 100644 index 0000000000..10b774eae7 --- /dev/null +++ b/Telegram/SourceFiles/rpl/distinct_until_changed.h @@ -0,0 +1,62 @@ +/* +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 "base/optional.h" + +namespace rpl { +namespace details { + +class distinct_until_changed_helper { +public: + template + rpl::producer operator()( + rpl::producer &&initial) const { + return [initial = std::move(initial)]( + const consumer &consumer) mutable { + auto previous = consumer.make_state< + base::optional + >(); + return std::move(initial).start( + [consumer, previous](Value &&value) { + if (!(*previous) || (**previous) != value) { + *previous = value; + consumer.put_next(std::move(value)); + } + }, [consumer](Error &&error) { + consumer.put_error(std::move(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 diff --git a/Telegram/SourceFiles/rpl/event_stream.h b/Telegram/SourceFiles/rpl/event_stream.h index bceb7750c7..0d17925f7e 100644 --- a/Telegram/SourceFiles/rpl/event_stream.h +++ b/Telegram/SourceFiles/rpl/event_stream.h @@ -20,36 +20,50 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #pragma once -#include "producer.h" +#include +#include +#include #include "base/algorithm.h" #include "base/assertion.h" #include "base/index_based_iterator.h" namespace rpl { -template +// Currently not thread-safe :( + +template class event_stream { public: event_stream(); event_stream(event_stream &&other); void fire(Value &&value); + void fire_copy(const Value &value) { + auto copy = value; + fire(std::move(copy)); + } producer events() const; + producer events_starting_with( + Value &&value) const { + return single(std::move(value)) | then(events()); + } + producer events_starting_with_copy( + const Value &value) const { + auto copy = value; + return events_starting_with(std::move(copy)); + } ~event_stream(); private: - std::weak_ptr>> weak() const { - return _consumers; - } + std::weak_ptr>> weak() const; - std::shared_ptr>> _consumers; + mutable std::shared_ptr>> _consumers; }; template -event_stream::event_stream() - : _consumers(std::make_shared>>()) { +event_stream::event_stream() { } template @@ -59,7 +73,10 @@ event_stream::event_stream(event_stream &&other) template void event_stream::fire(Value &&value) { - Expects(_consumers != nullptr); + if (!_consumers) { + return; + } + auto &consumers = *_consumers; auto begin = base::index_based_begin(consumers); auto end = base::index_based_end(consumers); @@ -97,7 +114,8 @@ void event_stream::fire(Value &&value) { template producer event_stream::events() const { - return producer([weak = weak()](consumer consumer) { + return producer([weak = weak()]( + const consumer &consumer) { if (auto strong = weak.lock()) { auto result = [weak, consumer] { if (auto strong = weak.lock()) { @@ -114,6 +132,15 @@ producer event_stream::events() const { }); } +template +std::weak_ptr>> event_stream::weak() const { + if (!_consumers) { + _consumers = std::make_shared>>(); + } + return _consumers; +} + + template event_stream::~event_stream() { if (_consumers) { @@ -123,4 +150,11 @@ event_stream::~event_stream() { } } +template +inline auto to_stream(event_stream &stream) { + return on_next([&stream](Value &&value) { + stream.fire(std::move(value)); + }); +} + } // namespace rpl diff --git a/Telegram/SourceFiles/rpl/fail.h b/Telegram/SourceFiles/rpl/fail.h new file mode 100644 index 0000000000..06c851f074 --- /dev/null +++ b/Telegram/SourceFiles/rpl/fail.h @@ -0,0 +1,37 @@ +/* +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 + +namespace rpl { + +template +producer> fail(Error &&error) { + using consumer_t = consumer>; + return [error = std::forward(error)]( + const consumer_t &consumer) mutable { + consumer.put_error(std::move(error)); + return lifetime(); + }; +} + +} // namespace rpl diff --git a/Telegram/SourceFiles/rpl/filter.h b/Telegram/SourceFiles/rpl/filter.h new file mode 100644 index 0000000000..ff6cc49b4f --- /dev/null +++ b/Telegram/SourceFiles/rpl/filter.h @@ -0,0 +1,75 @@ +/* +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 + +namespace rpl { +namespace details { + +template +class filter_helper { +public: + template + filter_helper(OtherPredicate &&predicate) + : _predicate(std::forward(predicate)) { + } + + template + rpl::producer operator()( + rpl::producer &&initial) { + return [ + initial = std::move(initial), + predicate = std::move(_predicate) + ]( + const consumer &consumer) mutable { + return std::move(initial).start( + [ + consumer, + predicate = std::move(predicate) + ](Value &&value) { + const auto &immutable = value; + if (predicate(immutable)) { + consumer.put_next(std::move(value)); + } + }, [consumer](Error &&error) { + consumer.put_error(std::move(error)); + }, [consumer] { + consumer.put_done(); + }); + }; + } + +private: + Predicate _predicate; + +}; + +} // namespace details + +template +auto filter(Predicate &&predicate) +-> details::filter_helper> { + return details::filter_helper>( + std::forward(predicate)); +} + +} // namespace rpl diff --git a/Telegram/SourceFiles/rpl/flatten_latest.h b/Telegram/SourceFiles/rpl/flatten_latest.h new file mode 100644 index 0000000000..b237c8d7df --- /dev/null +++ b/Telegram/SourceFiles/rpl/flatten_latest.h @@ -0,0 +1,82 @@ +/* +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 + +namespace rpl { +namespace details { + +class flatten_latest_helper { +public: + template + rpl::producer operator()( + rpl::producer< + rpl::producer, + Error + > &&initial) const { + return [initial = std::move(initial)]( + const consumer &consumer) mutable { + auto state = std::make_shared(); + return std::move(initial).start( + [consumer, state](rpl::producer &&inner) { + state->finished = false; + state->alive = std::move(inner).start( + [consumer](Value &&value) { + consumer.put_next(std::move(value)); + }, [consumer](Error &&error) { + consumer.put_error(std::move(error)); + }, [consumer, state] { + if (state->finished) { + consumer.put_done(); + } else { + state->finished = true; + } + }); + }, [consumer](Error &&error) { + consumer.put_error(std::move(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 + diff --git a/Telegram/SourceFiles/rpl/map.h b/Telegram/SourceFiles/rpl/map.h new file mode 100644 index 0000000000..1e7f28dd96 --- /dev/null +++ b/Telegram/SourceFiles/rpl/map.h @@ -0,0 +1,76 @@ +/* +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 + +namespace rpl { +namespace details { + +template +class map_helper { +public: + template + map_helper(OtherTransform &&transform) + : _transform(std::forward(transform)) { + } + + template < + typename Value, + typename Error, + typename NewValue = decltype( + std::declval()(std::declval()) + )> + rpl::producer operator()( + rpl::producer &&initial) { + return [ + initial = std::move(initial), + transform = std::move(_transform) + ](const consumer &consumer) mutable { + return std::move(initial).start( + [ + consumer, + transform = std::move(transform) + ](Value &&value) { + consumer.put_next(transform(std::move(value))); + }, [consumer](Error &&error) { + consumer.put_error(std::move(error)); + }, [consumer] { + consumer.put_done(); + }); + }; + } + +private: + Transform _transform; + +}; + +} // namespace details + +template +auto map(Transform &&transform) +-> details::map_helper> { + return details::map_helper>( + std::forward(transform)); +} + +} // namespace rpl diff --git a/Telegram/SourceFiles/rpl/never.h b/Telegram/SourceFiles/rpl/never.h new file mode 100644 index 0000000000..c3d32c1ddd --- /dev/null +++ b/Telegram/SourceFiles/rpl/never.h @@ -0,0 +1,34 @@ +/* +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 + +namespace rpl { + +template +producer never() { + return [](const consumer &consumer) mutable { + return lifetime(); + }; +} + +} // namespace rpl diff --git a/Telegram/SourceFiles/rpl/operators_tests.cpp b/Telegram/SourceFiles/rpl/operators_tests.cpp new file mode 100644 index 0000000000..0f1c43ce58 --- /dev/null +++ b/Telegram/SourceFiles/rpl/operators_tests.cpp @@ -0,0 +1,248 @@ +/* +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 +*/ +#include "catch.hpp" + +#include +#include + +using namespace rpl; + +class OnDestructor { +public: + OnDestructor(base::lambda_once callback) + : _callback(std::move(callback)) { + } + ~OnDestructor() { + if (_callback) { + _callback(); + } + } + +private: + base::lambda_once _callback; + +}; + +class InvokeCounter { +public: + InvokeCounter( + const std::shared_ptr ©Counter, + const std::shared_ptr &moveCounter) + : _copyCounter(copyCounter) + , _moveCounter(moveCounter) { + } + InvokeCounter(const InvokeCounter &other) + : _copyCounter(other._copyCounter) + , _moveCounter(other._moveCounter) { + if (_copyCounter) { + ++*_copyCounter; + } + } + InvokeCounter(InvokeCounter &&other) + : _copyCounter(base::take(other._copyCounter)) + , _moveCounter(base::take(other._moveCounter)) { + if (_moveCounter) { + ++*_moveCounter; + } + } + InvokeCounter &operator=(const InvokeCounter &other) { + _copyCounter = other._copyCounter; + _moveCounter = other._moveCounter; + if (_copyCounter) { + ++*_copyCounter; + } + } + InvokeCounter &operator=(InvokeCounter &&other) { + _copyCounter = base::take(other._copyCounter); + _moveCounter = base::take(other._moveCounter); + if (_moveCounter) { + ++*_moveCounter; + } + } + +private: + std::shared_ptr _copyCounter; + std::shared_ptr _moveCounter; + +}; + +TEST_CASE("basic operators tests", "[rpl::operators]") { + SECTION("single test") { + auto sum = std::make_shared(0); + auto doneGenerated = std::make_shared(false); + auto destroyed = std::make_shared(false); + auto copyCount = std::make_shared(0); + auto moveCount = std::make_shared(0); + { + InvokeCounter counter(copyCount, moveCount); + auto destroyCalled = std::make_shared([=] { + *destroyed = true; + }); + rpl::lifetime lifetime; + single(std::move(counter)) + | on_next([=](InvokeCounter&&) { + (void)destroyCalled; + ++*sum; + }) | on_error([=](no_error) { + (void)destroyCalled; + }) | on_done([=] { + (void)destroyCalled; + *doneGenerated = true; + }) | start(lifetime); + } + REQUIRE(*sum == 1); + REQUIRE(*doneGenerated); + REQUIRE(*destroyed); + REQUIRE(*copyCount == 0); + } + + SECTION("then test") { + auto sum = std::make_shared(0); + auto doneGenerated = std::make_shared(false); + auto destroyed = std::make_shared(false); + auto copyCount = std::make_shared(0); + auto moveCount = std::make_shared(0); + { + auto testing = complete(); + 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([=] { + *destroyed = true; + }); + + rpl::lifetime lifetime; + std::move(testing) + | then(complete()) + | on_next([=](InvokeCounter&&) { + (void)destroyCalled; + ++*sum; + }) | on_error([=](no_error) { + (void)destroyCalled; + }) | on_done([=] { + (void)destroyCalled; + *doneGenerated = true; + }) | start(lifetime); + } + REQUIRE(*sum == 5); + REQUIRE(*doneGenerated); + REQUIRE(*destroyed); + REQUIRE(*copyCount == 0); + } + + SECTION("map test") { + auto sum = std::make_shared(""); + { + 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) + ' '; + }) | start(lifetime); + } + REQUIRE(*sum == "1 2 3 4 5 "); + } + + SECTION("deferred test") { + auto launched = std::make_shared(0); + auto checked = std::make_shared(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); + }) | start(lifetime); + REQUIRE(*launched == 5); + } + } + + SECTION("filter test") { + auto sum = std::make_shared(""); + { + 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) + ' '; + }) | start(lifetime); + } + REQUIRE(*sum == "1 1 3 "); + } + + SECTION("distinct_until_changed test") { + auto sum = std::make_shared(""); + { + 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) + ' '; + }) | start(lifetime); + } + REQUIRE(*sum == "1 2 3 "); + } + + SECTION("flatten_latest test") { + auto sum = std::make_shared(""); + { + rpl::lifetime lifetime; + single(single(1) | then(single(2))) + | then(single(single(3) | then(single(4)))) + | then(single(single(5) | then(single(6)))) + | flatten_latest() + | map([](int value) { + return std::to_string(value); + }) | on_next([=](std::string &&value) { + *sum += std::move(value) + ' '; + }) | start(lifetime); + } + REQUIRE(*sum == "1 2 3 4 5 6 "); + } +} diff --git a/Telegram/SourceFiles/rpl/producer.h b/Telegram/SourceFiles/rpl/producer.h index b12bad7d1e..80dc063e4b 100644 --- a/Telegram/SourceFiles/rpl/producer.h +++ b/Telegram/SourceFiles/rpl/producer.h @@ -21,19 +21,70 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #pragma once #include "base/lambda.h" -#include "rpl/consumer.h" -#include "rpl/lifetime.h" +#include +#include namespace rpl { +namespace details { -template +template +class mutable_lambda_wrap { +public: + mutable_lambda_wrap(Lambda &&lambda) + : _lambda(std::move(lambda)) { + } + + template + auto operator()(Args&&... args) const { + return (const_cast(this)->_lambda)( + std::forward(args)...); + } + +private: + Lambda _lambda; + +}; + +// Type-erased copyable mutable lambda using base::lambda. +template class mutable_lambda; + +template +class mutable_lambda { +public: + + // Copy / move construct / assign from an arbitrary type. + template < + typename Lambda, + typename = std::enable_if_t()(std::declval()...)), + Return + >::value>> + mutable_lambda(Lambda other) : _implementation(mutable_lambda_wrap(std::move(other))) { + } + + template < + typename ...OtherArgs, + typename = std::enable_if_t<(sizeof...(Args) == sizeof...(OtherArgs))>> + Return operator()(OtherArgs&&... args) { + return _implementation(std::forward(args)...); + } + +private: + base::lambda _implementation; + +}; + +} // namespace details + +template class producer { public: using value_type = Value; using error_type = Error; + using consumer_type = consumer; template ()(std::declval>())), + decltype(std::declval()(std::declval())), lifetime >::value>> producer(Generator &&generator); @@ -48,10 +99,25 @@ public: lifetime start( OnNext &&next, OnError &&error, - OnDone &&done) const; + OnDone &&done) &&; + + template < + typename OnNext, + typename OnError, + typename OnDone, + typename = decltype(std::declval()(std::declval())), + typename = decltype(std::declval()(std::declval())), + typename = decltype(std::declval()())> + lifetime start_copy( + OnNext &&next, + OnError &&error, + OnDone &&done) const &; + + lifetime start_existing(const consumer_type &consumer) &&; private: - base::lambda)> _generator; + details::mutable_lambda< + lifetime(const consumer_type &)> _generator; }; @@ -72,13 +138,37 @@ template < lifetime producer::start( OnNext &&next, OnError &&error, - OnDone &&done) const { - auto result = consumer( + OnDone &&done) && { + return std::move(*this).start_existing(consumer( + std::forward(next), + std::forward(error), + std::forward(done))); +} + +template +template < + typename OnNext, + typename OnError, + typename OnDone, + typename, + typename, + typename> +lifetime producer::start_copy( + OnNext &&next, + OnError &&error, + OnDone &&done) const & { + auto copy = *this; + return std::move(copy).start( std::forward(next), std::forward(error), std::forward(done)); - result.set_lifetime(_generator(result)); - return [result] { result.terminate(); }; +} + +template +lifetime producer::start_existing( + const consumer_type &consumer) && { + consumer.add_lifetime(std::move(_generator)(consumer)); + return [consumer] { consumer.terminate(); }; } template @@ -91,21 +181,21 @@ template < typename Error, typename Method, typename = decltype(std::declval()(std::declval>()))> -inline decltype(auto) operator|(producer &&producer, Method &&method) { +inline auto operator|(producer &&producer, Method &&method) { return std::forward(method)(std::move(producer)); } template -inline decltype(auto) bind_on_next(OnNext &&handler) { +inline auto bind_on_next(OnNext &&handler) { return [handler = std::forward(handler)](auto &&existing) mutable { using value_type = typename std::decay_t::value_type; using error_type = typename std::decay_t::error_type; return producer([ existing = std::move(existing), - handler = std::forward(handler) - ](consumer consumer) { - return existing.start([handler = std::decay_t(handler)]( - value_type &&value) { + handler = std::move(handler) + ](const consumer &consumer) mutable { + return std::move(existing).start( + [handler = std::move(handler)](value_type &&value) { handler(std::move(value)); }, [consumer](error_type &&error) { consumer.put_error(std::move(error)); @@ -117,17 +207,18 @@ inline decltype(auto) bind_on_next(OnNext &&handler) { } template -inline decltype(auto) bind_on_error(OnError &&handler) { +inline auto bind_on_error(OnError &&handler) { return [handler = std::forward(handler)](auto &&existing) mutable { using value_type = typename std::decay_t::value_type; using error_type = typename std::decay_t::error_type; return producer([ existing = std::move(existing), - handler = std::forward(handler) - ](consumer consumer) { - return existing.start([consumer](value_type &&value) { + handler = std::move(handler) + ](const consumer &consumer) mutable { + return std::move(existing).start( + [consumer](value_type &&value) { consumer.put_next(std::move(value)); - }, [handler = std::decay_t(handler)](error_type &&error) { + }, [handler = std::move(handler)](error_type &&error) { handler(std::move(error)); }, [consumer] { consumer.put_done(); @@ -137,19 +228,20 @@ inline decltype(auto) bind_on_error(OnError &&handler) { } template -inline decltype(auto) bind_on_done(OnDone &&handler) { +inline auto bind_on_done(OnDone &&handler) { return [handler = std::forward(handler)](auto &&existing) mutable { using value_type = typename std::decay_t::value_type; using error_type = typename std::decay_t::error_type; return producer([ existing = std::move(existing), - handler = std::forward(handler) - ](consumer consumer) { - return existing.start([consumer](value_type &&value) { + handler = std::move(handler) + ](const consumer &consumer) mutable { + return std::move(existing).start( + [consumer](value_type &&value) { consumer.put_next(std::move(value)); }, [consumer](error_type &&value) { consumer.put_error(std::move(value)); - }, [handler = std::decay_t(handler)] { + }, [handler = std::move(handler)] { handler(); }); }); @@ -500,10 +592,11 @@ inline void operator|( OnError, OnDone> &&producer_with_next_error_done, lifetime_holder &&lifetime) { - lifetime.alive_while.add(producer_with_next_error_done.producer.start( - std::move(producer_with_next_error_done.next), - std::move(producer_with_next_error_done.error), - std::move(producer_with_next_error_done.done))); + lifetime.alive_while.add( + std::move(producer_with_next_error_done.producer).start( + std::move(producer_with_next_error_done.next), + std::move(producer_with_next_error_done.error), + std::move(producer_with_next_error_done.done))); } template diff --git a/Telegram/SourceFiles/rpl/producer_tests.cpp b/Telegram/SourceFiles/rpl/producer_tests.cpp index 5e8f524075..fcc9f6eaec 100644 --- a/Telegram/SourceFiles/rpl/producer_tests.cpp +++ b/Telegram/SourceFiles/rpl/producer_tests.cpp @@ -20,8 +20,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "catch.hpp" -#include "rpl/producer.h" -#include "rpl/event_stream.h" +#include +#include using namespace rpl; @@ -52,7 +52,7 @@ TEST_CASE("basic producer tests", "[rpl::producer]") { *destroyed = true; }); { - producer([=](auto consumer) { + producer([=](auto &&consumer) { (void)destroyCaller; consumer.put_next(1); consumer.put_next(2); @@ -82,7 +82,7 @@ TEST_CASE("basic producer tests", "[rpl::producer]") { SECTION("producer error test") { auto errorGenerated = std::make_shared(false); { - producer([=](auto consumer) { + producer([=](auto &&consumer) { consumer.put_error(true); return lifetime(); }).start([=](no_value) { @@ -99,16 +99,16 @@ TEST_CASE("basic producer tests", "[rpl::producer]") { { auto lifetimes = lifetime(); { - auto testProducer = producer([=](auto consumer) { + auto testProducer = producer([=](auto &&consumer) { return [=] { ++*lifetimeEndCount; }; }); - lifetimes.add(testProducer.start([=](no_value) { + lifetimes.add(testProducer.start_copy([=](no_value) { }, [=](no_error) { }, [=] { })); - lifetimes.add(testProducer.start([=](no_value) { + lifetimes.add(std::move(testProducer).start([=](no_value) { }, [=](no_error) { }, [=] { })); @@ -123,8 +123,8 @@ TEST_CASE("basic producer tests", "[rpl::producer]") { auto lifetimeEndCount = std::make_shared(0); auto saved = lifetime(); { - saved = producer([=](auto consumer) { - auto inner = producer([=](auto consumer) { + saved = producer([=](auto &&consumer) { + auto inner = producer([=](auto &&consumer) { consumer.put_next(1); consumer.put_next(2); consumer.put_next(3); @@ -135,12 +135,12 @@ TEST_CASE("basic producer tests", "[rpl::producer]") { auto result = lifetime([=] { ++*lifetimeEndCount; }); - result.add(inner.start([=](int value) { + result.add(inner.start_copy([=](int value) { consumer.put_next_copy(value); }, [=](no_error) { }, [=] { })); - result.add(inner.start([=](int value) { + result.add(std::move(inner).start([=](int value) { consumer.put_next_copy(value); }, [=](no_error) { }, [=] { @@ -157,7 +157,9 @@ TEST_CASE("basic producer tests", "[rpl::producer]") { saved.destroy(); REQUIRE(*lifetimeEndCount == 3); } +} +TEST_CASE("basic event_streams tests", "[rpl::event_stream]") { SECTION("event_stream basic test") { auto sum = std::make_shared(0); event_stream stream; @@ -326,7 +328,7 @@ TEST_CASE("basic piping tests", "[rpl::producer]") { auto doneGenerated = std::make_shared(false); { auto alive = lifetime(); - producer([=](auto consumer) { + producer([=](auto &&consumer) { consumer.put_next(1); consumer.put_next(2); consumer.put_next(3); @@ -338,7 +340,7 @@ TEST_CASE("basic piping tests", "[rpl::producer]") { *doneGenerated = true; }) | start(alive); - producer([=](auto consumer) { + producer([=](auto &&consumer) { consumer.put_error(4); return lifetime(); }) | bind_on_error([=](int value) { @@ -356,7 +358,7 @@ TEST_CASE("basic piping tests", "[rpl::producer]") { auto dones = std::make_shared(0); { auto alive = lifetime(); - producer([=](auto consumer) { + producer([=](auto &&consumer) { consumer.put_next(1); consumer.put_done(); return lifetime(); @@ -364,7 +366,7 @@ TEST_CASE("basic piping tests", "[rpl::producer]") { *sum += value; }) | start(alive); - producer([=](auto consumer) { + producer([=](auto &&consumer) { consumer.put_next(11); consumer.put_error(111); return lifetime(); @@ -372,7 +374,7 @@ TEST_CASE("basic piping tests", "[rpl::producer]") { *sum += value; }) | start(alive); - producer([=](auto consumer) { + producer([=](auto &&consumer) { consumer.put_next(1111); consumer.put_done(); return lifetime(); @@ -380,7 +382,7 @@ TEST_CASE("basic piping tests", "[rpl::producer]") { *dones += 1; }) | start(alive); - producer([=](auto consumer) { + producer([=](auto &&consumer) { consumer.put_next(11111); consumer.put_next(11112); consumer.put_next(11113); @@ -394,7 +396,7 @@ TEST_CASE("basic piping tests", "[rpl::producer]") { } auto alive = lifetime(); - producer([=](auto consumer) { + producer([=](auto &&consumer) { consumer.put_next(111111); consumer.put_next(111112); consumer.put_next(111113); @@ -406,7 +408,7 @@ TEST_CASE("basic piping tests", "[rpl::producer]") { *dones += 11; }) | start(alive); - producer([=](auto consumer) { + producer([=](auto &&consumer) { consumer.put_error(1111111); return lifetime(); }) | on_error([=](int value) { @@ -434,7 +436,7 @@ TEST_CASE("basic piping tests", "[rpl::producer]") { for (int i = 0; i != 3; ++i) { auto alive = lifetime(); - producer([=](auto consumer) { + producer([=](auto &&consumer) { consumer.put_next(1); consumer.put_done(); return lifetime(); diff --git a/Telegram/SourceFiles/rpl/rpl.h b/Telegram/SourceFiles/rpl/rpl.h new file mode 100644 index 0000000000..7db93de017 --- /dev/null +++ b/Telegram/SourceFiles/rpl/rpl.h @@ -0,0 +1,40 @@ +/* +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 +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include diff --git a/Telegram/SourceFiles/rpl/single.h b/Telegram/SourceFiles/rpl/single.h new file mode 100644 index 0000000000..8731315615 --- /dev/null +++ b/Telegram/SourceFiles/rpl/single.h @@ -0,0 +1,46 @@ +/* +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 + +namespace rpl { + +template +producer, Error> single(Value &&value) { + using consumer_t = consumer, Error>; + return [value = std::forward(value)]( + const consumer_t &consumer) mutable { + consumer.put_next(std::move(value)); + consumer.put_done(); + return lifetime(); + }; +} + +template +producer single() { + return [](const consumer &consumer) { + consumer.put_next({}); + consumer.put_done(); + }; +} + +} // namespace rpl diff --git a/Telegram/SourceFiles/rpl/then.h b/Telegram/SourceFiles/rpl/then.h new file mode 100644 index 0000000000..a2be3627ca --- /dev/null +++ b/Telegram/SourceFiles/rpl/then.h @@ -0,0 +1,58 @@ +/* +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 + +namespace rpl { + +template +auto then(producer &&following) { + return [following = std::move(following)]( + producer &&initial) mutable + -> producer { + return [ + initial = std::move(initial), + following = std::move(following) + ](const consumer &consumer) mutable { + return std::move(initial).start( + [consumer](Value &&value) { + consumer.put_next(std::move(value)); + }, [consumer](Error &&error) { + consumer.put_error(std::move(error)); + }, [ + consumer, + following = std::move(following) + ]() mutable { + consumer.add_lifetime(std::move(following).start( + [consumer](Value &&value) { + consumer.put_next(std::move(value)); + }, [consumer](Error &&error) { + consumer.put_error(std::move(error)); + }, [consumer] { + consumer.put_done(); + })); + }); + }; + }; +} + +} // namespace rpl diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 73ba528a8d..c6f1e71300 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -401,10 +401,6 @@ <(src_loc)/profile/profile_userpic_button.h <(src_loc)/profile/profile_widget.cpp <(src_loc)/profile/profile_widget.h -<(src_loc)/rpl/consumer.h -<(src_loc)/rpl/event_stream.h -<(src_loc)/rpl/lifetime.h -<(src_loc)/rpl/producer.h <(src_loc)/settings/settings_advanced_widget.cpp <(src_loc)/settings/settings_advanced_widget.h <(src_loc)/settings/settings_background_widget.cpp diff --git a/Telegram/gyp/tests/tests.gyp b/Telegram/gyp/tests/tests.gyp index b428e29a25..c3ddd5d5cc 100644 --- a/Telegram/gyp/tests/tests.gyp +++ b/Telegram/gyp/tests/tests.gyp @@ -92,16 +92,29 @@ '<(src_loc)/base/flat_set_tests.cpp', ], }, { - 'target_name': 'tests_producer', + 'target_name': 'tests_rpl', 'includes': [ 'common_test.gypi', ], 'sources': [ + '<(src_loc)/rpl/before_next.h', + '<(src_loc)/rpl/complete.h', '<(src_loc)/rpl/consumer.h', + '<(src_loc)/rpl/deferred.h', + '<(src_loc)/rpl/distinct_until_changed.h', '<(src_loc)/rpl/event_stream.h', + '<(src_loc)/rpl/fail.h', + '<(src_loc)/rpl/filter.h', + '<(src_loc)/rpl/flatten_latest.h', '<(src_loc)/rpl/lifetime.h', + '<(src_loc)/rpl/map.h', + '<(src_loc)/rpl/never.h', + '<(src_loc)/rpl/operators_tests.cpp', '<(src_loc)/rpl/producer.h', '<(src_loc)/rpl/producer_tests.cpp', + '<(src_loc)/rpl/rpl.h', + '<(src_loc)/rpl/single.h', + '<(src_loc)/rpl/then.h', ], }], } diff --git a/Telegram/gyp/tests/tests_list.txt b/Telegram/gyp/tests/tests_list.txt index d2220189f9..c80f601f24 100644 --- a/Telegram/gyp/tests/tests_list.txt +++ b/Telegram/gyp/tests/tests_list.txt @@ -2,4 +2,4 @@ tests_algorithm tests_flags tests_flat_map tests_flat_set -tests_producer +tests_rpl \ No newline at end of file