From e9ac4bdfae29c4e769ff53cfecfac5e1ecd1293d Mon Sep 17 00:00:00 2001 From: Aleksey Loginov Date: Sun, 20 Nov 2022 18:27:18 +0300 Subject: [PATCH 1/9] Initial implementation --- cmake/dependencies.cmake | 3 +- .../rppqt/doxygen/main_thread_scheduler.cpp | 0 .../rpp/schedulers/immediate_scheduler.hpp | 1 - src/rppqt/rppqt/fwd.hpp | 10 ++- src/rppqt/rppqt/rppqt.hpp | 3 +- src/rppqt/rppqt/schedulers.hpp | 20 +++++ src/rppqt/rppqt/schedulers/fwd.hpp | 16 ++++ .../schedulers/main_thread_scheduler.hpp | 78 +++++++++++++++++++ src/rppqt/rppqt/utils/exceptions.hpp | 21 +++++ .../rppqt/test_main_thread_scheduler.cpp | 17 ++++ 10 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 src/examples/rppqt/doxygen/main_thread_scheduler.cpp create mode 100644 src/rppqt/rppqt/schedulers.hpp create mode 100644 src/rppqt/rppqt/schedulers/fwd.hpp create mode 100644 src/rppqt/rppqt/schedulers/main_thread_scheduler.hpp create mode 100644 src/rppqt/rppqt/utils/exceptions.hpp create mode 100644 src/tests/rppqt/test_main_thread_scheduler.cpp diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake index 9e3306e43..3bdf266c9 100644 --- a/cmake/dependencies.cmake +++ b/cmake/dependencies.cmake @@ -7,10 +7,11 @@ endif() # ==================== QT ========================== if (RPP_BUILD_QT_CODE AND (RPP_BUILD_TESTS OR RPP_BUILD_EXAMPLES)) - find_package(Qt6 COMPONENTS Widgets) + find_package(Qt6 COMPONENTS Widgets QUIET) if (Qt6_FOUND) SET(RPP_QT_TARGET Qt6) else() + message("-- RPP: Can't find Qt6, searching for Qt5...") find_package(Qt5 REQUIRED COMPONENTS Widgets) SET(RPP_QT_TARGET Qt5) endif() diff --git a/src/examples/rppqt/doxygen/main_thread_scheduler.cpp b/src/examples/rppqt/doxygen/main_thread_scheduler.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/src/rpp/rpp/schedulers/immediate_scheduler.hpp b/src/rpp/rpp/schedulers/immediate_scheduler.hpp index 959cadc8e..568cb9072 100644 --- a/src/rpp/rpp/schedulers/immediate_scheduler.hpp +++ b/src/rpp/rpp/schedulers/immediate_scheduler.hpp @@ -17,7 +17,6 @@ #include #include -#include namespace rpp::schedulers { diff --git a/src/rppqt/rppqt/fwd.hpp b/src/rppqt/rppqt/fwd.hpp index fe87166d5..59f19315b 100644 --- a/src/rppqt/rppqt/fwd.hpp +++ b/src/rppqt/rppqt/fwd.hpp @@ -21,4 +21,12 @@ * \ingroup rppqt */ -#include \ No newline at end of file +#include + +/** + * \defgroup schedulers Schedulers + * \brief Scheduler is the way to introduce multi-threading in your application via RPP + * \see https://reactivex.io/documentation/scheduler.html + * \ingroup rpp + */ +#include diff --git a/src/rppqt/rppqt/rppqt.hpp b/src/rppqt/rppqt/rppqt.hpp index 0fefc0e95..4fcf1e0db 100644 --- a/src/rppqt/rppqt/rppqt.hpp +++ b/src/rppqt/rppqt/rppqt.hpp @@ -11,4 +11,5 @@ #pragma once #include -#include \ No newline at end of file +#include +#include \ No newline at end of file diff --git a/src/rppqt/rppqt/schedulers.hpp b/src/rppqt/rppqt/schedulers.hpp new file mode 100644 index 000000000..4a2e95043 --- /dev/null +++ b/src/rppqt/rppqt/schedulers.hpp @@ -0,0 +1,20 @@ +// ReactivePlusPlus library +// +// Copyright Aleksey Loginov 2022 - present. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) +// +// Project home: https://github.com/victimsnino/ReactivePlusPlus +// + +#pragma once + +/** + * \defgroup qt_schedulers QT Schedulers + * \brief Scheduler is the way to introduce multi-threading in your application via RPP + * \see https://reactivex.io/documentation/scheduler.html + * \ingroup rppqt + */ + +#include \ No newline at end of file diff --git a/src/rppqt/rppqt/schedulers/fwd.hpp b/src/rppqt/rppqt/schedulers/fwd.hpp new file mode 100644 index 000000000..756c4be14 --- /dev/null +++ b/src/rppqt/rppqt/schedulers/fwd.hpp @@ -0,0 +1,16 @@ +// ReactivePlusPlus library +// +// Copyright Aleksey Loginov 2022 - present. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) +// +// Project home: https://github.com/victimsnino/ReactivePlusPlus +// + +#pragma once + +namespace rppqt::schedulers +{ +class main_thread_scheduler; +} // namespace rppqt::schedulers diff --git a/src/rppqt/rppqt/schedulers/main_thread_scheduler.hpp b/src/rppqt/rppqt/schedulers/main_thread_scheduler.hpp new file mode 100644 index 000000000..f4db83f5a --- /dev/null +++ b/src/rppqt/rppqt/schedulers/main_thread_scheduler.hpp @@ -0,0 +1,78 @@ +// ReactivePlusPlus library +// +// Copyright Aleksey Loginov 2022 - present. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) +// +// Project home: https://github.com/victimsnino/ReactivePlusPlus +// + +#pragma once + +#include // own forwarding +#include // worker +#include // lifetime +#include + +#include +#include + +#include +#include + +namespace rppqt::schedulers +{ +/** + * \brief Schedule provided schedulables to main GUI QT thread (where QApplication placed) + * \ingroup qt_schedulers + */ +class main_thread_scheduler final : public rpp::schedulers::details::scheduler_tag +{ +private: + class worker_strategy; + using main_thread_schedulable = rpp::schedulers::schedulable_wrapper; + + class worker_strategy + { + public: + worker_strategy(const rpp::subscription_base& sub) + : m_sub{sub} {} + + void defer_at(rpp::schedulers::time_point time_point, rpp::schedulers::constraint::schedulable_fn auto&& fn) const + { + defer_at(time_point, main_thread_schedulable{*this, time_point, std::forward(fn)}); + } + + void defer_at(rpp::schedulers::time_point time_point, main_thread_schedulable&& fn) const + { + if (!m_sub.is_subscribed()) + return; + + const auto application = QApplication::instance(); + if (!application) + throw utils::no_active_qapplication{ + "Pointer to application is null. Create QApplication before using main_thread_scheduler!"}; + + const auto duration = std::chrono::duration_cast(now() - time_point).count(); + QTimer::singleShot(duration, + application, + [fn = std::move(fn), sub=m_sub]() mutable + { + if (sub.is_subscribed()) + fn(); + }); + } + + static rpp::schedulers::time_point now() { return rpp::schedulers::clock_type::now(); } + private: + rpp::subscription_base m_sub; + }; + +public: + static auto create_worker(const rpp::subscription_base& sub = {}) + { + return rpp::schedulers::worker{sub}; + } +}; +} // namespace rppqt::schedulers diff --git a/src/rppqt/rppqt/utils/exceptions.hpp b/src/rppqt/rppqt/utils/exceptions.hpp new file mode 100644 index 000000000..24f9e1ddb --- /dev/null +++ b/src/rppqt/rppqt/utils/exceptions.hpp @@ -0,0 +1,21 @@ +// ReactivePlusPlus library +// +// Copyright Aleksey Loginov 2022 - present. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) +// +// Project home: https://github.com/victimsnino/ReactivePlusPlus +// + +#pragma once + +#include + +namespace rppqt::utils +{ +struct no_active_qapplication : std::runtime_error +{ + using std::runtime_error::runtime_error; +}; +} \ No newline at end of file diff --git a/src/tests/rppqt/test_main_thread_scheduler.cpp b/src/tests/rppqt/test_main_thread_scheduler.cpp new file mode 100644 index 000000000..9c731f05c --- /dev/null +++ b/src/tests/rppqt/test_main_thread_scheduler.cpp @@ -0,0 +1,17 @@ +// ReactivePlusPlus library +// +// Copyright Aleksey Loginov 2022 - present. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) +// +// Project home: https://github.com/victimsnino/ReactivePlusPlus + +#include + +#include + +SCENARIO("main_thread_scheduler schedules actions to main thread", "[schedulers]") +{ + +} \ No newline at end of file From 84e72f7f0fc011eae728f2611532d2962728d8ff Mon Sep 17 00:00:00 2001 From: Aleksey Loginov Date: Sun, 20 Nov 2022 22:46:26 +0300 Subject: [PATCH 2/9] Add tests --- .../rppqt/test_main_thread_scheduler.cpp | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/tests/rppqt/test_main_thread_scheduler.cpp b/src/tests/rppqt/test_main_thread_scheduler.cpp index 9c731f05c..ce503a2bc 100644 --- a/src/tests/rppqt/test_main_thread_scheduler.cpp +++ b/src/tests/rppqt/test_main_thread_scheduler.cpp @@ -8,10 +8,38 @@ // Project home: https://github.com/victimsnino/ReactivePlusPlus #include +#include "mock_observer.hpp" #include +#include +#include + SCENARIO("main_thread_scheduler schedules actions to main thread", "[schedulers]") { - + GIVEN("qapplication and scheduler") + { + int argc{}; + QApplication application{argc, nullptr}; + WHEN("submitting action to main scheduler from another thread") + { + std::promise execution_thread{}; + std::thread{[&] + { + rppqt::schedulers::main_thread_scheduler{}.create_worker().schedule([&]()->rpp::schedulers::optional_duration + { + execution_thread.set_value(std::this_thread::get_id()); + application.exit(); + return {}; + }); + }}.join(); + application.exec(); + THEN("thread of exectuion of schedulable should be same as thread of application") + { + auto future = execution_thread.get_future(); + REQUIRE(future.wait_for(std::chrono::seconds{3}) == std::future_status::ready); + CHECK(future.get() == std::this_thread::get_id()); + } + } + } } \ No newline at end of file From 4e0415e69f1e2fcc8832042be4159e58e9b577fd Mon Sep 17 00:00:00 2001 From: Aleksey Loginov Date: Sun, 20 Nov 2022 22:46:36 +0300 Subject: [PATCH 3/9] Minor --- src/tests/rppqt/test_main_thread_scheduler.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tests/rppqt/test_main_thread_scheduler.cpp b/src/tests/rppqt/test_main_thread_scheduler.cpp index ce503a2bc..e1bf29db2 100644 --- a/src/tests/rppqt/test_main_thread_scheduler.cpp +++ b/src/tests/rppqt/test_main_thread_scheduler.cpp @@ -8,7 +8,6 @@ // Project home: https://github.com/victimsnino/ReactivePlusPlus #include -#include "mock_observer.hpp" #include From a7e0e8ef2c7f2f7020c817924c1c100bbe05bd60 Mon Sep 17 00:00:00 2001 From: Aleksey Loginov Date: Sun, 20 Nov 2022 22:50:02 +0300 Subject: [PATCH 4/9] Extend tests --- .../rppqt/test_main_thread_scheduler.cpp | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/tests/rppqt/test_main_thread_scheduler.cpp b/src/tests/rppqt/test_main_thread_scheduler.cpp index e1bf29db2..a2d98bb1e 100644 --- a/src/tests/rppqt/test_main_thread_scheduler.cpp +++ b/src/tests/rppqt/test_main_thread_scheduler.cpp @@ -8,6 +8,7 @@ // Project home: https://github.com/victimsnino/ReactivePlusPlus #include +#include #include @@ -28,10 +29,10 @@ SCENARIO("main_thread_scheduler schedules actions to main thread", "[schedulers] rppqt::schedulers::main_thread_scheduler{}.create_worker().schedule([&]()->rpp::schedulers::optional_duration { execution_thread.set_value(std::this_thread::get_id()); - application.exit(); return {}; }); }}.join(); + QTimer::singleShot(10, &application, [&]{application.exit();}); application.exec(); THEN("thread of exectuion of schedulable should be same as thread of application") { @@ -40,5 +41,26 @@ SCENARIO("main_thread_scheduler schedules actions to main thread", "[schedulers] CHECK(future.get() == std::this_thread::get_id()); } } + WHEN("submitting action to main scheduler from another thread but with unsubscribed subscription after schedule") + { + std::promise execution_thread{}; + std::thread{[&] + { + rpp::composite_subscription sub{}; + rppqt::schedulers::main_thread_scheduler{}.create_worker(sub).schedule([&]()->rpp::schedulers::optional_duration + { + execution_thread.set_value(std::this_thread::get_id()); + return {}; + }); + sub.unsubscribe(); + }}.join(); + QTimer::singleShot(10, &application, [&]{application.exit();}); + application.exec(); + THEN("no schedule execution") + { + auto future = execution_thread.get_future(); + REQUIRE(future.wait_for(std::chrono::seconds{1}) == std::future_status::timeout); + } + } } } \ No newline at end of file From 0cf446685e6ec6a05c5b38b4c257f6f1eb4368e5 Mon Sep 17 00:00:00 2001 From: Aleksey Loginov Date: Sun, 20 Nov 2022 22:50:37 +0300 Subject: [PATCH 5/9] remove --- src/examples/rppqt/doxygen/main_thread_scheduler.cpp | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/examples/rppqt/doxygen/main_thread_scheduler.cpp diff --git a/src/examples/rppqt/doxygen/main_thread_scheduler.cpp b/src/examples/rppqt/doxygen/main_thread_scheduler.cpp deleted file mode 100644 index e69de29bb..000000000 From 47c4fc25ec39f0b8111c5b3da14af9220ab9b66b Mon Sep 17 00:00:00 2001 From: Aleksey Loginov Date: Sun, 20 Nov 2022 23:03:27 +0300 Subject: [PATCH 6/9] try args --- src/tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 1461ab1e3..713137598 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -21,7 +21,7 @@ macro(rpp_register_tests module) target_link_libraries(${TARGET} PRIVATE Catch2::Catch2WithMain rpp_tests_utils ${module}) set_target_properties(${TARGET} PROPERTIES FOLDER Tests/Suites/${module}) - add_test(NAME ${TARGET} COMMAND $ -r junit -o ${RPP_TEST_RESULTS_DIR}/${TARGET}.xml) + add_test(NAME ${TARGET} COMMAND $ -r junit -o ${RPP_TEST_RESULTS_DIR}/${TARGET}.xml -platform offscreen) if (${module} STREQUAL rppqt) rpp_add_qt_support_to_executable(${TARGET}) From da43af660c9a48f4be78adc3225afa82e989b03d Mon Sep 17 00:00:00 2001 From: Aleksey Loginov Date: Sun, 20 Nov 2022 23:17:13 +0300 Subject: [PATCH 7/9] adapt rebase --- src/rppqt/rppqt/schedulers/main_thread_scheduler.hpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/rppqt/rppqt/schedulers/main_thread_scheduler.hpp b/src/rppqt/rppqt/schedulers/main_thread_scheduler.hpp index f4db83f5a..f3280b038 100644 --- a/src/rppqt/rppqt/schedulers/main_thread_scheduler.hpp +++ b/src/rppqt/rppqt/schedulers/main_thread_scheduler.hpp @@ -39,6 +39,8 @@ class main_thread_scheduler final : public rpp::schedulers::details::scheduler_t worker_strategy(const rpp::subscription_base& sub) : m_sub{sub} {} + bool is_subscribed() const { return m_sub.is_subscribed(); } + void defer_at(rpp::schedulers::time_point time_point, rpp::schedulers::constraint::schedulable_fn auto&& fn) const { defer_at(time_point, main_thread_schedulable{*this, time_point, std::forward(fn)}); @@ -55,13 +57,7 @@ class main_thread_scheduler final : public rpp::schedulers::details::scheduler_t "Pointer to application is null. Create QApplication before using main_thread_scheduler!"}; const auto duration = std::chrono::duration_cast(now() - time_point).count(); - QTimer::singleShot(duration, - application, - [fn = std::move(fn), sub=m_sub]() mutable - { - if (sub.is_subscribed()) - fn(); - }); + QTimer::singleShot(duration, application, std::move(fn)); } static rpp::schedulers::time_point now() { return rpp::schedulers::clock_type::now(); } From 8666da9e30b17a3f3a75115634b5cdeb5f163fdb Mon Sep 17 00:00:00 2001 From: Aleksey Loginov Date: Sun, 20 Nov 2022 23:19:21 +0300 Subject: [PATCH 8/9] Try one more --- CMakePresets.json | 3 +++ src/tests/CMakeLists.txt | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakePresets.json b/CMakePresets.json index f2f01e677..d3ae34570 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -224,6 +224,9 @@ { "name": "ci-tests", "configuration": "Release", + "environment": { + "QT_QPA_PLATFORM":"offscreen" + }, "output": { "outputOnFailure": true }, diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 713137598..1461ab1e3 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -21,7 +21,7 @@ macro(rpp_register_tests module) target_link_libraries(${TARGET} PRIVATE Catch2::Catch2WithMain rpp_tests_utils ${module}) set_target_properties(${TARGET} PROPERTIES FOLDER Tests/Suites/${module}) - add_test(NAME ${TARGET} COMMAND $ -r junit -o ${RPP_TEST_RESULTS_DIR}/${TARGET}.xml -platform offscreen) + add_test(NAME ${TARGET} COMMAND $ -r junit -o ${RPP_TEST_RESULTS_DIR}/${TARGET}.xml) if (${module} STREQUAL rppqt) rpp_add_qt_support_to_executable(${TARGET}) From 37c5b0574a9d6aa89cf1339e35c4cae27b2881a2 Mon Sep 17 00:00:00 2001 From: Aleksey Loginov Date: Mon, 21 Nov 2022 22:39:53 +0300 Subject: [PATCH 9/9] Try to simplify --- src/rppqt/rppqt/schedulers/main_thread_scheduler.hpp | 4 ++-- src/tests/rppqt/test_main_thread_scheduler.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rppqt/rppqt/schedulers/main_thread_scheduler.hpp b/src/rppqt/rppqt/schedulers/main_thread_scheduler.hpp index f3280b038..103ab6bbf 100644 --- a/src/rppqt/rppqt/schedulers/main_thread_scheduler.hpp +++ b/src/rppqt/rppqt/schedulers/main_thread_scheduler.hpp @@ -18,7 +18,7 @@ #include #include -#include +#include #include namespace rppqt::schedulers @@ -51,7 +51,7 @@ class main_thread_scheduler final : public rpp::schedulers::details::scheduler_t if (!m_sub.is_subscribed()) return; - const auto application = QApplication::instance(); + const auto application = QCoreApplication::instance(); if (!application) throw utils::no_active_qapplication{ "Pointer to application is null. Create QApplication before using main_thread_scheduler!"}; diff --git a/src/tests/rppqt/test_main_thread_scheduler.cpp b/src/tests/rppqt/test_main_thread_scheduler.cpp index a2d98bb1e..ff18d3987 100644 --- a/src/tests/rppqt/test_main_thread_scheduler.cpp +++ b/src/tests/rppqt/test_main_thread_scheduler.cpp @@ -20,7 +20,7 @@ SCENARIO("main_thread_scheduler schedules actions to main thread", "[schedulers] GIVEN("qapplication and scheduler") { int argc{}; - QApplication application{argc, nullptr}; + QCoreApplication application{argc, nullptr}; WHEN("submitting action to main scheduler from another thread") { std::promise execution_thread{};