140 lines
4.9 KiB
C++
140 lines
4.9 KiB
C++
#ifndef BOOSTTHREADWORKER_HPP
|
|
#define BOOSTTHREADWORKER_HPP
|
|
|
|
#include <boost/variant.hpp>
|
|
|
|
#include "Worker.hpp"
|
|
|
|
#include <libslic3r/Thread.hpp>
|
|
#include <boost/log/trivial.hpp>
|
|
|
|
#include "ThreadSafeQueue.hpp"
|
|
|
|
namespace Slic3r { namespace GUI {
|
|
|
|
// An implementation of the Worker interface which uses the boost::thread
|
|
// API and two thread safe message queues to communicate with the main thread
|
|
// back and forth. The queue from the main thread to the worker thread holds the
|
|
// job entries that will be performed on the worker. The other queue holds messages
|
|
// from the worker to the main thread. These messages include status updates,
|
|
// finishing operation and arbitrary functiors that need to be performed
|
|
// on the main thread during the jobs execution, like displaying intermediate
|
|
// results.
|
|
class BoostThreadWorker : public Worker, private Job::Ctl
|
|
{
|
|
struct JobEntry // Goes into worker and also out of worker as a finalize msg
|
|
{
|
|
std::unique_ptr<Job> job;
|
|
bool canceled = false;
|
|
std::exception_ptr eptr = nullptr;
|
|
};
|
|
|
|
// A message data for status updates. Only goes from worker to main thread.
|
|
struct StatusInfo { int status; std::string msg; };
|
|
|
|
// An arbitrary callback to be called on the main thread. Only from worker
|
|
// to main thread.
|
|
struct MainThreadCallData
|
|
{
|
|
std::function<void()> fn;
|
|
std::promise<void> promise;
|
|
};
|
|
|
|
struct EmptyMessage {};
|
|
|
|
class WorkerMessage
|
|
{
|
|
public:
|
|
enum MsgType { Empty, Status, Finalize, MainThreadCall };
|
|
|
|
private:
|
|
boost::variant<EmptyMessage, StatusInfo, JobEntry, MainThreadCallData> m_data;
|
|
|
|
public:
|
|
WorkerMessage() = default;
|
|
WorkerMessage(int s, std::string txt)
|
|
: m_data{StatusInfo{s, std::move(txt)}}
|
|
{}
|
|
WorkerMessage(JobEntry &&entry) : m_data{std::move(entry)} {}
|
|
WorkerMessage(MainThreadCallData fn) : m_data{std::move(fn)} {}
|
|
|
|
int get_type () const { return m_data.which(); }
|
|
|
|
void deliver(BoostThreadWorker &runner);
|
|
};
|
|
|
|
using JobQueue = ThreadSafeQueueSPSC<JobEntry>;
|
|
using MessageQueue = ThreadSafeQueueSPSC<WorkerMessage>;
|
|
|
|
boost::thread m_thread;
|
|
std::atomic<bool> m_running{false}, m_canceled{false};
|
|
std::shared_ptr<ProgressIndicator> m_progress;
|
|
JobQueue m_input_queue; // from main thread to worker
|
|
MessageQueue m_output_queue; // form worker to main thread
|
|
std::string m_name;
|
|
|
|
void run();
|
|
|
|
bool join(int timeout_ms = 0);
|
|
|
|
protected:
|
|
// Implement Job::Ctl interface:
|
|
|
|
void update_status(int st, const std::string &msg = "") override;
|
|
|
|
bool was_canceled() const override { return m_canceled.load(); }
|
|
|
|
std::future<void> call_on_main_thread(std::function<void()> fn) override;
|
|
|
|
public:
|
|
explicit BoostThreadWorker(std::shared_ptr<ProgressIndicator> pri,
|
|
boost::thread::attributes & attr,
|
|
const char * name = "");
|
|
|
|
explicit BoostThreadWorker(std::shared_ptr<ProgressIndicator> pri,
|
|
boost::thread::attributes && attr,
|
|
const char * name = "")
|
|
: BoostThreadWorker{std::move(pri), attr, name}
|
|
{}
|
|
|
|
explicit BoostThreadWorker(std::shared_ptr<ProgressIndicator> pri,
|
|
const char * name = "")
|
|
: BoostThreadWorker{std::move(pri), {}, name}
|
|
{}
|
|
|
|
~BoostThreadWorker();
|
|
|
|
BoostThreadWorker(const BoostThreadWorker &) = delete;
|
|
BoostThreadWorker(BoostThreadWorker &&) = delete;
|
|
BoostThreadWorker &operator=(const BoostThreadWorker &) = delete;
|
|
BoostThreadWorker &operator=(BoostThreadWorker &&) = delete;
|
|
|
|
bool push(std::unique_ptr<Job> job) override;
|
|
|
|
bool is_idle() const override
|
|
{
|
|
// The assumption is that jobs can only be queued from a single main
|
|
// thread from which this method is also called. And the output
|
|
// messages are also processed only in this calling thread. In that
|
|
// case, if the input queue is empty, it will remain so during this
|
|
// function call. If the worker thread is also not running and the
|
|
// output queue is already processed, we can safely say that the
|
|
// worker is dormant.
|
|
return m_input_queue.empty() && !m_running.load() && m_output_queue.empty();
|
|
}
|
|
|
|
void cancel() override { m_canceled.store(true); }
|
|
void cancel_all() override { m_input_queue.clear(); cancel(); }
|
|
|
|
ProgressIndicator * get_pri() { return m_progress.get(); }
|
|
const ProgressIndicator * get_pri() const { return m_progress.get(); }
|
|
|
|
void process_events() override;
|
|
bool wait_for_current_job(unsigned timeout_ms = 0) override;
|
|
bool wait_for_idle(unsigned timeout_ms = 0) override;
|
|
|
|
};
|
|
|
|
}} // namespace Slic3r::GUI
|
|
|
|
#endif // BOOSTTHREADWORKER_HPP
|