From ebe4bbbf0f0df1a989b7b21a0c723a86c0c6bb77 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 3 Sep 2017 15:44:21 +0300 Subject: [PATCH] Add core rpl::producer/consumer implementation. --- Telegram/SourceFiles/base/assertion.h | 10 + Telegram/SourceFiles/base/tests_main.cpp | 30 ++- Telegram/SourceFiles/rpl/consumer.h | 242 ++++++++++++++++++++ Telegram/SourceFiles/rpl/lifetime.h | 76 ++++++ Telegram/SourceFiles/rpl/producer.h | 81 +++++++ Telegram/SourceFiles/rpl/producer_tests.cpp | 159 +++++++++++++ Telegram/gyp/telegram_sources.txt | 3 + Telegram/gyp/tests/tests.gyp | 11 + Telegram/gyp/tests/tests_list.txt | 1 + 9 files changed, 606 insertions(+), 7 deletions(-) create mode 100644 Telegram/SourceFiles/rpl/consumer.h create mode 100644 Telegram/SourceFiles/rpl/lifetime.h create mode 100644 Telegram/SourceFiles/rpl/producer.h create mode 100644 Telegram/SourceFiles/rpl/producer_tests.cpp diff --git a/Telegram/SourceFiles/base/assertion.h b/Telegram/SourceFiles/base/assertion.h index 33de049a9..9d1e0039e 100644 --- a/Telegram/SourceFiles/base/assertion.h +++ b/Telegram/SourceFiles/base/assertion.h @@ -43,10 +43,20 @@ inline constexpr void noop() { std::abort(); } +#ifndef GSL_UNLIKELY +#define DEFINED_GSL_UNLIKELY_ +#define GSL_UNLIKELY(expression) (expression) +#endif // GSL_UNLIKELY + inline constexpr void validate(bool condition, const char *message, const char *file, int line) { (GSL_UNLIKELY(!(condition))) ? fail(message, file, line) : noop(); } +#ifdef DEFINED_GSL_UNLIKELY_ +#undef GSL_UNLIKELY +#undef DEFINED_GSL_UNLIKELY_ +#endif // DEFINED_GSL_UNLIKELY_ + } // namespace assertion } // namespace base diff --git a/Telegram/SourceFiles/base/tests_main.cpp b/Telegram/SourceFiles/base/tests_main.cpp index 0696e477e..fa1af779a 100644 --- a/Telegram/SourceFiles/base/tests_main.cpp +++ b/Telegram/SourceFiles/base/tests_main.cpp @@ -23,6 +23,17 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "reporters/catch_reporter_compact.hpp" #include +namespace base { +namespace assertion { + +// For Assert() / Expects() / Ensures() / Unexpected() to work. +void log(const char *message, const char *file, int line) { + std::cout << message << " (" << file << ":" << line << ")" << std::endl; +} + +} // namespace assertion +} // namespace base + namespace Catch { struct MinimalReporter : CompactReporter { @@ -82,15 +93,20 @@ namespace Catch { } // end namespace Catch int main(int argc, const char *argv[]) { - const char *catch_argv[] = { argv[0], "-r", "minimal" }; + auto touchFile = QString(); + for (auto i = 0; i != argc; ++i) { + if (argv[i] == QString("--touch") && i + 1 != argc) { + touchFile = QFile::decodeName(argv[++i]); + } + } + const char *catch_argv[] = { + argv[0], + touchFile.isEmpty() ? "-b" : "-r", + touchFile.isEmpty() ? "-b" : "minimal" }; constexpr auto catch_argc = sizeof(catch_argv) / sizeof(catch_argv[0]); auto result = Catch::Session().run(catch_argc, catch_argv); - if (result == 0) { - for (auto i = 0; i != argc; ++i) { - if (argv[i] == QString("--touch") && i + 1 != argc) { - QFile(QFile::decodeName(argv[++i])).open(QIODevice::WriteOnly); - } - } + if (result == 0 && !touchFile.isEmpty()) { + QFile(touchFile).open(QIODevice::WriteOnly); } return (result < 0xff ? result : 0xff); } diff --git a/Telegram/SourceFiles/rpl/consumer.h b/Telegram/SourceFiles/rpl/consumer.h new file mode 100644 index 000000000..01529b416 --- /dev/null +++ b/Telegram/SourceFiles/rpl/consumer.h @@ -0,0 +1,242 @@ +/* +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 "rpl/lifetime.h" +#include + +namespace rpl { + +struct no_value { +}; + +struct no_error { + no_error() = delete; +}; + +template +class consumer { +public: + template < + typename OnNext, + typename OnError, + typename OnDone, + typename = decltype(std::declval()(std::declval())), + typename = decltype(std::declval()(std::declval())), + typename = decltype(std::declval()())> + consumer( + OnNext &&next, + OnError &&error, + OnDone &&done); + + bool putNext(Value value) const; + void putError(Error error) const; + void putDone() const; + + void setLifetime(lifetime &&lifetime) const; + void terminate() const; + +private: + class abstract_consumer_instance; + + template + class consumer_instance; + + template + std::shared_ptr ConstructInstance( + OnNext &&next, + OnError &&error, + OnDone &&done); + + std::shared_ptr _instance; + +}; + + +template +class consumer::abstract_consumer_instance { +public: + virtual bool putNext(Value value) = 0; + virtual void putError(Error error) = 0; + virtual void putDone() = 0; + + void setLifetime(lifetime &&lifetime); + void terminate(); + +protected: + lifetime _lifetime; + bool _terminated = false; + std::mutex _mutex; + +}; + +template +template +class consumer::consumer_instance + : public consumer::abstract_consumer_instance { +public: + template + consumer_instance( + OnNextImpl &&next, + OnErrorImpl &&error, + OnDoneImpl &&done) + : _next(std::forward(next)) + , _error(std::forward(error)) + , _done(std::forward(done)) { + } + + bool putNext(Value value) override; + void putError(Error error) override; + void putDone() override; + +private: + OnNext _next; + OnError _error; + OnDone _done; + +}; + +template +template +std::shared_ptr::abstract_consumer_instance> +consumer::ConstructInstance( + OnNext &&next, + OnError &&error, + OnDone &&done) { + return std::make_shared, + std::decay_t, + std::decay_t>>( + std::forward(next), + std::forward(error), + std::forward(done)); +} + +template +template < + typename OnNext, + typename OnError, + typename OnDone, + typename, + typename, + typename> +consumer::consumer( + OnNext &&next, + OnError &&error, + OnDone &&done) : _instance(ConstructInstance( + std::forward(next), + std::forward(error), + std::forward(done))) { +} + +template +bool consumer::putNext(Value value) const { + return _instance->putNext(std::move(value)); +} + +template +void consumer::putError(Error error) const { + return _instance->putError(std::move(error)); +} + +template +void consumer::putDone() const { + return _instance->putDone(); +} + +template +void consumer::setLifetime(lifetime &&lifetime) const { + return _instance->setLifetime(std::move(lifetime)); +} + +template +void consumer::terminate() const { + return _instance->terminate(); +} + +template +void consumer::abstract_consumer_instance::setLifetime( + lifetime &&lifetime) { + std::unique_lock lock(_mutex); + if (_terminated) { + lock.unlock(); + + lifetime.destroy(); + } else { + _lifetime = std::move(lifetime); + } +} + +template +void consumer::abstract_consumer_instance::terminate() { + std::unique_lock lock(_mutex); + if (!_terminated) { + _terminated = true; + auto handler = std::exchange(_lifetime, lifetime()); + lock.unlock(); + + handler.destroy(); + } +} + +template +template +bool consumer::consumer_instance::putNext( + Value value) { + std::unique_lock lock(this->_mutex); + if (this->_terminated) { + return false; + } + auto handler = this->_next; + lock.unlock(); + + handler(std::move(value)); + return true; +} + +template +template +void consumer::consumer_instance::putError( + Error error) { + std::unique_lock lock(this->_mutex); + if (!this->_terminated) { + auto handler = std::move(this->_error); + lock.unlock(); + + handler(std::move(error)); + this->terminate(); + } +} + +template +template +void consumer::consumer_instance::putDone() { + std::unique_lock lock(this->_mutex); + if (!this->_terminated) { + auto handler = std::move(this->_done); + lock.unlock(); + + handler(); + this->terminate(); + } +} + +} // namespace rpl diff --git a/Telegram/SourceFiles/rpl/lifetime.h b/Telegram/SourceFiles/rpl/lifetime.h new file mode 100644 index 000000000..171e1621b --- /dev/null +++ b/Telegram/SourceFiles/rpl/lifetime.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 "base/lambda.h" +#include "base/algorithm.h" +#include + +namespace rpl { + +class lifetime { +public: + lifetime() = default; + lifetime(lifetime &&other); + lifetime &operator=(lifetime &&other); + + template ()())> + lifetime(Destroy &&destroy) : _destroy(std::forward(destroy)) { + } + + void add(lifetime other) { + _nested.push_back(std::move(other)); + } + + void destroy() { + auto nested = std::exchange(_nested, std::vector()); + auto callback = std::exchange(_destroy, base::lambda_once()); + + if (!nested.empty()) { + nested.clear(); + } + if (callback) { + callback(); + } + } + + ~lifetime() { + destroy(); + } + +private: + base::lambda_once _destroy; + std::vector _nested; + +}; + +lifetime::lifetime(lifetime &&other) +: _destroy(std::exchange(other._destroy, base::lambda_once())) +, _nested(std::exchange(other._nested, std::vector())) { +} + +lifetime &lifetime::operator=(lifetime &&other) { + _destroy = std::exchange(other._destroy, base::lambda_once()); + _nested = std::exchange(other._nested, std::vector()); + return *this; +} + +} // namespace rpl diff --git a/Telegram/SourceFiles/rpl/producer.h b/Telegram/SourceFiles/rpl/producer.h new file mode 100644 index 000000000..22fdb5cae --- /dev/null +++ b/Telegram/SourceFiles/rpl/producer.h @@ -0,0 +1,81 @@ +/* +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 "base/lambda.h" +#include "rpl/consumer.h" +#include "rpl/lifetime.h" + +namespace rpl { + +template +class producer { +public: + template ()(std::declval>())), + lifetime + >::value>> + producer(Generator &&generator); + + 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( + OnNext &&next, + OnError &&error, + OnDone &&done); + +private: + base::lambda)> _generator; + +}; + +template +template +producer::producer(Generator &&generator) + : _generator(std::forward(generator)) { +} + +template +template < + typename OnNext, + typename OnError, + typename OnDone, + typename, + typename, + typename> +lifetime producer::start( + OnNext &&next, + OnError &&error, + OnDone &&done) { + auto result = consumer( + std::forward(next), + std::forward(error), + std::forward(done)); + result.setLifetime(_generator(result)); + return [result] { result.terminate(); }; +} + +} // namespace rpl diff --git a/Telegram/SourceFiles/rpl/producer_tests.cpp b/Telegram/SourceFiles/rpl/producer_tests.cpp new file mode 100644 index 000000000..e7861c8a1 --- /dev/null +++ b/Telegram/SourceFiles/rpl/producer_tests.cpp @@ -0,0 +1,159 @@ +/* +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 "rpl/producer.h" + +using namespace rpl; + +class OnDestructor { +public: + OnDestructor(base::lambda_once callback) : _callback(std::move(callback)) { + } + ~OnDestructor() { + if (_callback) { + _callback(); + } + } + +private: + base::lambda_once _callback; + +}; + +TEST_CASE("basic producer tests", "[rpl::producer]") { + SECTION("producer next, done and lifetime end test") { + auto lifetimeEnded = std::make_shared(false); + auto sum = std::make_shared(0); + auto doneGenerated = std::make_shared(false); + auto destroyed = std::make_shared(false); + { + auto destroyCaller = std::make_shared([=] { + *destroyed = true; + }); + { + producer([=](auto consumer) { + destroyCaller; + consumer.putNext(1); + consumer.putNext(2); + consumer.putNext(3); + consumer.putDone(); + return [=] { + destroyCaller; + *lifetimeEnded = true; + }; + }).start([=](int value) { + destroyCaller; + *sum += value; + }, [=](no_error) { + destroyCaller; + }, [=]() { + destroyCaller; + *doneGenerated = true; + }); + } + } + REQUIRE(*sum == 1 + 2 + 3); + REQUIRE(*doneGenerated); + REQUIRE(*lifetimeEnded); + REQUIRE(*destroyed); + } + + SECTION("producer error test") { + auto errorGenerated = std::make_shared(false); + { + producer([=](auto consumer) { + consumer.putError(true); + return lifetime(); + }).start([=](no_value) { + }, [=](bool error) { + *errorGenerated = error; + }, [=]() { + }); + } + REQUIRE(*errorGenerated); + } + + SECTION("nested lifetimes test") { + auto lifetimeEndCount = std::make_shared(0); + { + auto lifetimes = lifetime(); + { + auto testProducer = producer([=](auto consumer) { + return [=] { + ++*lifetimeEndCount; + }; + }); + lifetimes.add(testProducer.start([=](no_value) { + }, [=](no_error) { + }, [=] { + })); + lifetimes.add(testProducer.start([=](no_value) { + }, [=](no_error) { + }, [=] { + })); + } + REQUIRE(*lifetimeEndCount == 0); + } + REQUIRE(*lifetimeEndCount == 2); + } + + SECTION("nested producers test") { + auto sum = std::make_shared(0); + auto lifetimeEndCount = std::make_shared(0); + auto saved = lifetime(); + { + saved = producer([=](auto consumer) { + auto inner = producer([=](auto consumer) { + consumer.putNext(1); + consumer.putNext(2); + consumer.putNext(3); + return [=] { + ++*lifetimeEndCount; + }; + }); + auto result = lifetime([=] { + ++*lifetimeEndCount; + }); + result.add(inner.start([=](int value) { + consumer.putNext(value); + }, [=](no_error) { + }, [=] { + })); + result.add(inner.start([=](int value) { + consumer.putNext(value); + }, [=](no_error) { + }, [=] { + })); + return result; + }).start([=](int value) { + *sum += value; + }, [=](no_error) { + }, [=] { + }); + } + REQUIRE(*sum == 1 + 2 + 3 + 1 + 2 + 3); + REQUIRE(*lifetimeEndCount == 0); + saved.destroy(); + REQUIRE(*lifetimeEndCount == 3); + } +} + diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index fd17f0ea3..a4d28535d 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -393,6 +393,9 @@ <(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/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 42284ddff..9cef4e41a 100644 --- a/Telegram/gyp/tests/tests.gyp +++ b/Telegram/gyp/tests/tests.gyp @@ -82,5 +82,16 @@ '<(src_loc)/base/flags.h', '<(src_loc)/base/flags_tests.cpp', ], + }, { + 'target_name': 'tests_producer', + 'includes': [ + 'common_test.gypi', + ], + 'sources': [ + '<(src_loc)/rpl/consumer.h', + '<(src_loc)/rpl/lifetime.h', + '<(src_loc)/rpl/producer.h', + '<(src_loc)/rpl/producer_tests.cpp', + ], }], } diff --git a/Telegram/gyp/tests/tests_list.txt b/Telegram/gyp/tests/tests_list.txt index 6cb15eda3..a901682b3 100644 --- a/Telegram/gyp/tests/tests_list.txt +++ b/Telegram/gyp/tests/tests_list.txt @@ -1,3 +1,4 @@ tests_flat_map tests_flat_set tests_flags +tests_producer