#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