Skip to content

Commit

Permalink
Add UIThreadWorker for debugging and profiling purposes
Browse files Browse the repository at this point in the history
  • Loading branch information
tamasmeszaros committed May 27, 2022
1 parent a115702 commit 9892893
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 14 deletions.
1 change: 1 addition & 0 deletions src/slic3r/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/slic3r/GUI/Jobs/PlaterWorker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(); }
Expand Down
114 changes: 114 additions & 0 deletions src/slic3r/GUI/Jobs/UIThreadWorker.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#ifndef UITHREADWORKER_HPP
#define UITHREADWORKER_HPP

#include <deque>
#include <queue>

#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::unique_ptr<Job>, std::deque<std::unique_ptr<Job>>> m_jobqueue;
std::shared_ptr<ProgressIndicator> m_progress;
bool m_running = false;
bool m_canceled = false;

void process_front()
{
std::unique_ptr<Job> 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<void> call_on_main_thread(std::function<void()> fn) override
{
return std::async(std::launch::deferred, [fn]{ fn(); });
}

public:
explicit UIThreadWorker(std::shared_ptr<ProgressIndicator> pri,
const std::string & /*name*/ = "")
: m_progress{pri}
{}

UIThreadWorker() = default;

bool push(std::unique_ptr<Job> 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
3 changes: 3 additions & 0 deletions src/slic3r/GUI/Plater.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<BoostThreadWorker> m_worker;
SLAImportDialog * m_sla_import_dlg;

Expand Down
40 changes: 26 additions & 14 deletions tests/slic3rutils/slic3r_jobs_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
#include <thread>

#include "slic3r/GUI/Jobs/BoostThreadWorker.hpp"
#include "slic3r/GUI/Jobs/UIThreadWorker.hpp"
#include "slic3r/GUI/Jobs/ProgressIndicator.hpp"

//#include <boost/thread/thread.hpp>

struct Progress: Slic3r::ProgressIndicator {
int range = 100;
int pr = 0;
Expand All @@ -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<Progress>()};
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<Progress>()};

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<Progress>()};
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<Progress>(), "worker_thread"};
TestType worker{std::make_unique<Progress>(), "worker_thread"};

queue_job(worker, [&worker](Job::Ctl &ctl) {
ctl.call_on_main_thread([&worker] {
Expand All @@ -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<Progress>();
BoostThreadWorker worker{pri};
TestType worker{pri};

queue_job(worker, [](Job::Ctl &ctl){
for (int s = 0; s <= 100; ++s) {
Expand All @@ -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<Progress>();
BoostThreadWorker worker{pri};
TestType worker{pri};

queue_job(
worker,
Expand All @@ -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<Progress>();
BoostThreadWorker worker{pri};
TestType worker{pri};

std::array<bool, 4> jobres = {false, false, false, false};

Expand Down Expand Up @@ -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<Progress>();
BoostThreadWorker worker{pri};
TestType worker{pri};

queue_job(
worker, [](Job::Ctl &) { throw std::runtime_error("test"); },
Expand Down

0 comments on commit 9892893

Please sign in to comment.