From 98928935877b3ec25a62c8fec1fc28642fff6b23 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 27 May 2022 15:33:03 +0200 Subject: [PATCH] Add UIThreadWorker for debugging and profiling purposes --- src/slic3r/CMakeLists.txt | 1 + src/slic3r/GUI/Jobs/PlaterWorker.hpp | 6 ++ src/slic3r/GUI/Jobs/UIThreadWorker.hpp | 114 ++++++++++++++++++++++++ src/slic3r/GUI/Plater.cpp | 3 + tests/slic3rutils/slic3r_jobs_tests.cpp | 40 ++++++--- 5 files changed, 150 insertions(+), 14 deletions(-) create mode 100644 src/slic3r/GUI/Jobs/UIThreadWorker.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 09c2098d9b3..78e73ba9aa5 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -172,6 +172,7 @@ set(SLIC3R_GUI_SOURCES GUI/Jobs/Worker.hpp GUI/Jobs/BoostThreadWorker.hpp GUI/Jobs/BoostThreadWorker.cpp + GUI/Jobs/UIThreadWorker.hpp GUI/Jobs/BusyCursorJob.hpp GUI/Jobs/PlaterWorker.hpp GUI/Jobs/ArrangeJob.hpp diff --git a/src/slic3r/GUI/Jobs/PlaterWorker.hpp b/src/slic3r/GUI/Jobs/PlaterWorker.hpp index 57359027287..58bd1ec32bd 100644 --- a/src/slic3r/GUI/Jobs/PlaterWorker.hpp +++ b/src/slic3r/GUI/Jobs/PlaterWorker.hpp @@ -40,6 +40,12 @@ class PlaterWorker: public Worker { { wxWakeUpIdle(); ctl.update_status(st, msg); + + // If the worker is not using additional threads, the UI + // is refreshed with this call. If the worker is running + // in it's own thread, the yield should not have any + // visible effects. + wxYieldIfNeeded(); } bool was_canceled() const override { return ctl.was_canceled(); } diff --git a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp new file mode 100644 index 00000000000..aa946036e34 --- /dev/null +++ b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp @@ -0,0 +1,114 @@ +#ifndef UITHREADWORKER_HPP +#define UITHREADWORKER_HPP + +#include +#include + +#include "Worker.hpp" +#include "ProgressIndicator.hpp" + +namespace Slic3r { namespace GUI { + +// Implementation of a worker which does not create any additional threads. +class UIThreadWorker : public Worker, private Job::Ctl { + std::queue, std::deque>> m_jobqueue; + std::shared_ptr m_progress; + bool m_running = false; + bool m_canceled = false; + + void process_front() + { + std::unique_ptr job; + + if (!m_jobqueue.empty()) { + job = std::move(m_jobqueue.front()); + m_jobqueue.pop(); + } + + if (job) { + std::exception_ptr eptr; + m_running = true; + + try { + job->process(*this); + } catch (...) { + eptr= std::current_exception(); + } + + m_running = false; + + job->finalize(m_canceled, eptr); + + m_canceled = false; + } + } + +protected: + // Implement Job::Ctl interface: + + void update_status(int st, const std::string &msg = "") override + { + if (m_progress) { + m_progress->set_progress(st); + m_progress->set_status_text(msg.c_str()); + } + } + + bool was_canceled() const override { return m_canceled; } + + std::future call_on_main_thread(std::function fn) override + { + return std::async(std::launch::deferred, [fn]{ fn(); }); + } + +public: + explicit UIThreadWorker(std::shared_ptr pri, + const std::string & /*name*/ = "") + : m_progress{pri} + {} + + UIThreadWorker() = default; + + bool push(std::unique_ptr job) override + { + m_canceled = false; + m_jobqueue.push(std::move(job)); + + return bool(job); + } + + bool is_idle() const override { return !m_running; } + + void cancel() override { m_canceled = true; } + + void cancel_all() override + { + m_canceled = true; + process_front(); + while (!m_jobqueue.empty()) m_jobqueue.pop(); + } + + void process_events() override { + while (!m_jobqueue.empty()) + process_front(); + } + + bool wait_for_current_job(unsigned /*timeout_ms*/ = 0) override { + process_front(); + + return true; + } + + bool wait_for_idle(unsigned /*timeout_ms*/ = 0) override { + process_events(); + + return true; + } + + ProgressIndicator * get_pri() { return m_progress.get(); } + const ProgressIndicator * get_pri() const { return m_progress.get(); } +}; + +}} // namespace Slic3r::GUI + +#endif // UITHREADWORKER_HPP diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0456936e440..30c30d2eceb 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1648,6 +1648,9 @@ struct Plater::priv // objects would be frozen for the user. In case of arrange, an animation // could be shown, or with the optimize orientations, partial results // could be displayed. + // + // UIThreadWorker can be used as a replacement for BoostThreadWorker if + // no additional worker threads are desired (useful for debugging or profiling) PlaterWorker m_worker; SLAImportDialog * m_sla_import_dlg; diff --git a/tests/slic3rutils/slic3r_jobs_tests.cpp b/tests/slic3rutils/slic3r_jobs_tests.cpp index d31b07349a0..d4b912b84f6 100644 --- a/tests/slic3rutils/slic3r_jobs_tests.cpp +++ b/tests/slic3rutils/slic3r_jobs_tests.cpp @@ -4,10 +4,9 @@ #include #include "slic3r/GUI/Jobs/BoostThreadWorker.hpp" +#include "slic3r/GUI/Jobs/UIThreadWorker.hpp" #include "slic3r/GUI/Jobs/ProgressIndicator.hpp" -//#include - struct Progress: Slic3r::ProgressIndicator { int range = 100; int pr = 0; @@ -19,17 +18,30 @@ struct Progress: Slic3r::ProgressIndicator { int get_range() const override { return range; } }; -TEST_CASE("nullptr job should be ignored", "[Jobs]") { - Slic3r::GUI::BoostThreadWorker worker{std::make_unique()}; +using TestClasses = std::tuple< Slic3r::GUI::UIThreadWorker, Slic3r::GUI::BoostThreadWorker >; + +TEMPLATE_LIST_TEST_CASE("Empty worker should not do anything", "[Jobs]", TestClasses) { + TestType worker{std::make_unique()}; + + REQUIRE(worker.is_idle()); + + worker.wait_for_current_job(); + worker.process_events(); + + REQUIRE(worker.is_idle()); +} + +TEMPLATE_LIST_TEST_CASE("nullptr job should be ignored", "[Jobs]", TestClasses) { + TestType worker{std::make_unique()}; worker.push(nullptr); REQUIRE(worker.is_idle()); } -TEST_CASE("State should not be idle while running a job", "[Jobs]") { +TEMPLATE_LIST_TEST_CASE("State should not be idle while running a job", "[Jobs]", TestClasses) { using namespace Slic3r; using namespace Slic3r::GUI; - BoostThreadWorker worker{std::make_unique(), "worker_thread"}; + TestType worker{std::make_unique(), "worker_thread"}; queue_job(worker, [&worker](Job::Ctl &ctl) { ctl.call_on_main_thread([&worker] { @@ -42,11 +54,11 @@ TEST_CASE("State should not be idle while running a job", "[Jobs]") { REQUIRE(worker.is_idle()); } -TEST_CASE("Status messages should be received by the main thread during job execution", "[Jobs]") { +TEMPLATE_LIST_TEST_CASE("Status messages should be received by the main thread during job execution", "[Jobs]", TestClasses) { using namespace Slic3r; using namespace Slic3r::GUI; auto pri = std::make_shared(); - BoostThreadWorker worker{pri}; + TestType worker{pri}; queue_job(worker, [](Job::Ctl &ctl){ for (int s = 0; s <= 100; ++s) { @@ -60,12 +72,12 @@ TEST_CASE("Status messages should be received by the main thread during job exec REQUIRE(pri->statustxt == "Running"); } -TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]") { +TEMPLATE_LIST_TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]", TestClasses) { using namespace Slic3r; using namespace Slic3r::GUI; auto pri = std::make_shared(); - BoostThreadWorker worker{pri}; + TestType worker{pri}; queue_job( worker, @@ -88,12 +100,12 @@ TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]") { REQUIRE(pri->pr != 100); } -TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]") { +TEMPLATE_LIST_TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]", TestClasses) { using namespace Slic3r; using namespace Slic3r::GUI; auto pri = std::make_shared(); - BoostThreadWorker worker{pri}; + TestType worker{pri}; std::array jobres = {false, false, false, false}; @@ -125,12 +137,12 @@ TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]") { REQUIRE(jobres[3] == false); } -TEST_CASE("Exception should be properly forwarded to finalize()", "[Jobs]") { +TEMPLATE_LIST_TEST_CASE("Exception should be properly forwarded to finalize()", "[Jobs]", TestClasses) { using namespace Slic3r; using namespace Slic3r::GUI; auto pri = std::make_shared(); - BoostThreadWorker worker{pri}; + TestType worker{pri}; queue_job( worker, [](Job::Ctl &) { throw std::runtime_error("test"); },