#ifndef JOB_HPP #define JOB_HPP #include #include #include #include #include #include namespace Slic3r { namespace GUI { // A class to handle UI jobs like arranging and optimizing rotation. // These are not instant jobs, the user has to be informed about their // state in the status progress indicator. On the other hand they are // separated from the background slicing process. Ideally, these jobs should // run when the background process is not running. // // TODO: A mechanism would be useful for blocking the plater interactions: // 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. class Job : public wxEvtHandler { int m_range = 100; boost::thread m_thread; std::atomic m_running{false}, m_canceled{false}; bool m_finalized = false; std::shared_ptr m_progress; void run() { m_running.store(true); process(); m_running.store(false); // ensure to call the last status to finalize the job update_status(status_range(), ""); } protected: // status range for a particular job virtual int status_range() const { return 100; } // status update, to be used from the work thread (process() method) void update_status(int st, const wxString &msg = "") { auto evt = new wxThreadEvent(); evt->SetInt(st); evt->SetString(msg); wxQueueEvent(this, evt); } bool was_canceled() const { return m_canceled.load(); } // Launched just before start(), a job can use it to prepare internals virtual void prepare() {} // Launched when the job is finished. It refreshes the 3Dscene by def. virtual void finalize() { m_finalized = true; } public: Job(std::shared_ptr pri) : m_progress(pri) { Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) { auto msg = evt.GetString(); if (!msg.empty()) m_progress->set_status_text(msg.ToUTF8().data()); if (m_finalized) return; m_progress->set_progress(evt.GetInt()); if (evt.GetInt() == status_range()) { // set back the original range and cancel callback m_progress->set_range(m_range); m_progress->set_cancel_callback(); wxEndBusyCursor(); finalize(); // dont do finalization again for the same process m_finalized = true; } }); } bool is_finalized() const { return m_finalized; } Job(const Job &) = delete; Job(Job &&) = delete; Job &operator=(const Job &) = delete; Job &operator=(Job &&) = delete; virtual void process() = 0; void start() { // Start the job. No effect if the job is already running if (!m_running.load()) { prepare(); // Save the current status indicatior range and push the new one m_range = m_progress->get_range(); m_progress->set_range(status_range()); // init cancellation flag and set the cancel callback m_canceled.store(false); m_progress->set_cancel_callback( [this]() { m_canceled.store(true); }); m_finalized = false; // Changing cursor to busy wxBeginBusyCursor(); try { // Execute the job m_thread = create_thread([this] { this->run(); }); } catch (std::exception &) { update_status(status_range(), _(L("ERROR: not enough resources to " "execute a new job."))); } // The state changes will be undone when the process hits the // last status value, in the status update handler (see ctor) } } // To wait for the running job and join the threads. False is // returned if the timeout has been reached and the job is still // running. Call cancel() before this fn if you want to explicitly // end the job. bool join(int timeout_ms = 0) { if (!m_thread.joinable()) return true; if (timeout_ms <= 0) m_thread.join(); else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms))) return false; return true; } bool is_running() const { return m_running.load(); } void cancel() { m_canceled.store(true); } }; } } #endif // JOB_HPP