#ifndef slic3r_PrintBase_hpp_ #define slic3r_PrintBase_hpp_ #include "libslic3r.h" #include #include #include #include // tbb/mutex.h includes Windows, which in turn defines min/max macros. Convince Windows.h to not define these min/max macros. #ifndef NOMINMAX #define NOMINMAX #endif #include "tbb/mutex.h" #include "Model.hpp" #include "PrintConfig.hpp" namespace Slic3r { class CanceledException : public std::exception { public: const char* what() const throw() { return "Background processing has been canceled"; } }; class PrintStateBase { public: enum State { INVALID, STARTED, DONE, }; typedef size_t TimeStamp; // A new unique timestamp is being assigned to the step every time the step changes its state. struct StateWithTimeStamp { StateWithTimeStamp() : state(INVALID), timestamp(0) {} State state; TimeStamp timestamp; }; protected: //FIXME last timestamp is shared between Print & SLAPrint, // and if multiple Print or SLAPrint instances are executed in parallel, modification of g_last_timestamp // is not synchronized! static size_t g_last_timestamp; }; // To be instantiated over PrintStep or PrintObjectStep enums. template class PrintState : public PrintStateBase { public: PrintState() {} StateWithTimeStamp state_with_timestamp(StepType step, tbb::mutex &mtx) const { tbb::mutex::scoped_lock lock(mtx); StateWithTimeStamp state = m_state[step]; return state; } bool is_done(StepType step, tbb::mutex &mtx) const { return this->state_with_timestamp(step, mtx).state == DONE; } StateWithTimeStamp state_with_timestamp_unguarded(StepType step) const { return m_state[step]; } bool is_done_unguarded(StepType step) const { return this->state_with_timestamp_unguarded(step).state == DONE; } // Set the step as started. Block on mutex while the Print / PrintObject / PrintRegion objects are being // modified by the UI thread. // This is necessary to block until the Print::apply_config() updates its state, which may // influence the processing step being entered. template bool set_started(StepType step, tbb::mutex &mtx, ThrowIfCanceled throw_if_canceled) { tbb::mutex::scoped_lock lock(mtx); // If canceled, throw before changing the step state. throw_if_canceled(); if (m_state[step].state == DONE) return false; m_state[step].state = STARTED; m_state[step].timestamp = ++ g_last_timestamp; return true; } // Set the step as done. Block on mutex while the Print / PrintObject / PrintRegion objects are being // modified by the UI thread. template TimeStamp set_done(StepType step, tbb::mutex &mtx, ThrowIfCanceled throw_if_canceled) { tbb::mutex::scoped_lock lock(mtx); // If canceled, throw before changing the step state. throw_if_canceled(); assert(m_state[step].state != DONE); m_state[step].state = DONE; m_state[step].timestamp = ++ g_last_timestamp; return m_state[step].timestamp; } // Make the step invalid. // PrintBase::m_state_mutex should be locked at this point, guarding access to m_state. // In case the step has already been entered or finished, cancel the background // processing by calling the cancel callback. template bool invalidate(StepType step, CancelationCallback cancel) { bool invalidated = m_state[step].state != INVALID; if (invalidated) { #if 0 if (mtx.state != mtx.HELD) { printf("Not held!\n"); } #endif m_state[step].state = INVALID; m_state[step].timestamp = ++ g_last_timestamp; // Raise the mutex, so that the following cancel() callback could cancel // the background processing. // Internally the cancel() callback shall unlock the PrintBase::m_status_mutex to let // the working thread to proceed. cancel(); } return invalidated; } template bool invalidate_multiple(StepTypeIterator step_begin, StepTypeIterator step_end, CancelationCallback cancel) { bool invalidated = false; for (StepTypeIterator it = step_begin; it != step_end; ++ it) { StateWithTimeStamp &state = m_state[*it]; if (state.state != INVALID) { invalidated = true; state.state = INVALID; state.timestamp = ++ g_last_timestamp; } } if (invalidated) { #if 0 if (mtx.state != mtx.HELD) { printf("Not held!\n"); } #endif // Raise the mutex, so that the following cancel() callback could cancel // the background processing. // Internally the cancel() callback shall unlock the PrintBase::m_status_mutex to let // the working thread to proceed. cancel(); } return invalidated; } // Make all steps invalid. // PrintBase::m_state_mutex should be locked at this point, guarding access to m_state. // In case any step has already been entered or finished, cancel the background // processing by calling the cancel callback. template bool invalidate_all(CancelationCallback cancel) { bool invalidated = false; for (size_t i = 0; i < COUNT; ++ i) { StateWithTimeStamp &state = m_state[i]; if (state.state != INVALID) { invalidated = true; state.state = INVALID; state.timestamp = ++ g_last_timestamp; } } if (invalidated) cancel(); return invalidated; } private: StateWithTimeStamp m_state[COUNT]; }; class PrintBase; class PrintObjectBase { public: const ModelObject* model_object() const { return m_model_object; } ModelObject* model_object() { return m_model_object; } protected: PrintObjectBase(ModelObject *model_object) : m_model_object(model_object) {} virtual ~PrintObjectBase() {} // Declared here to allow access from PrintBase through friendship. static tbb::mutex& state_mutex(PrintBase *print); static std::function cancel_callback(PrintBase *print); ModelObject *m_model_object; }; /** * @brief Printing involves slicing and export of device dependent instructions. * * Every technology has a potentially different set of requirements for * slicing, support structures and output print instructions. The pipeline * however remains roughly the same: * slice -> convert to instructions -> send to printer * * The PrintBase class will abstract this flow for different technologies. * */ class PrintBase { public: PrintBase() { this->restart(); } inline virtual ~PrintBase() {} virtual PrinterTechnology technology() const noexcept = 0; // Reset the print status including the copy of the Model / ModelObject hierarchy. virtual void clear() = 0; // The Print is empty either after clear() or after apply() over an empty model, // or after apply() over a model, where no object is printable (all outside the print volume). virtual bool empty() const = 0; // Validate the print, return empty string if valid, return error if process() cannot (or should not) be started. virtual std::string validate() const { return std::string(); } enum ApplyStatus { // No change after the Print::apply() call. APPLY_STATUS_UNCHANGED, // Some of the Print / PrintObject / PrintObjectInstance data was changed, // but no result was invalidated (only data influencing not yet calculated results were changed). APPLY_STATUS_CHANGED, // Some data was changed, which in turn invalidated already calculated steps. APPLY_STATUS_INVALIDATED, }; virtual ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) = 0; const Model& model() const { return m_model; } virtual void process() = 0; struct SlicingStatus { SlicingStatus(int percent, const std::string &text, unsigned int flags = 0) : percent(percent), text(text), flags(flags) {} int percent; std::string text; // Bitmap of flags. enum FlagBits { DEFAULT, NO_RELOAD_SCENE = 0, RELOAD_SCENE = 1, }; // Bitmap of FlagBits unsigned int flags; }; typedef std::function status_callback_type; // Default status console print out in the form of percent => message. void set_status_default() { m_status_callback = nullptr; } // No status output or callback whatsoever, useful mostly for automatic tests. void set_status_silent() { m_status_callback = [](const SlicingStatus&){}; } // Register a custom status callback. void set_status_callback(status_callback_type cb) { m_status_callback = cb; } // Calls a registered callback to update the status, or print out the default message. void set_status(int percent, const std::string &message, unsigned int flags = SlicingStatus::DEFAULT) { if (m_status_callback) m_status_callback(SlicingStatus(percent, message, flags)); else printf("%d => %s\n", percent, message.c_str()); } typedef std::function cancel_callback_type; // Various methods will call this callback to stop the background processing (the Print::process() call) // in case a successive change of the Print / PrintObject / PrintRegion instances changed // the state of the finished or running calculations. void set_cancel_callback(cancel_callback_type cancel_callback) { m_cancel_callback = cancel_callback; } // Has the calculation been canceled? enum CancelStatus { // No cancelation, background processing should run. NOT_CANCELED = 0, // Canceled by user from the user interface (user pressed the "Cancel" button or user closed the application). CANCELED_BY_USER = 1, // Canceled internally from Print::apply() through the Print/PrintObject::invalidate_step() or ::invalidate_all_steps(). CANCELED_INTERNAL = 2 }; CancelStatus cancel_status() const { return m_cancel_status; } // Has the calculation been canceled? bool canceled() const { return m_cancel_status != NOT_CANCELED; } // Cancel the running computation. Stop execution of all the background threads. void cancel() { m_cancel_status = CANCELED_BY_USER; } void cancel_internal() { m_cancel_status = CANCELED_INTERNAL; } // Cancel the running computation. Stop execution of all the background threads. void restart() { m_cancel_status = NOT_CANCELED; } protected: friend class PrintObjectBase; friend class BackgroundSlicingProcess; tbb::mutex& state_mutex() const { return m_state_mutex; } std::function cancel_callback() { return m_cancel_callback; } void call_cancell_callback() { m_cancel_callback(); } // If the background processing stop was requested, throw CanceledException. // To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly. void throw_if_canceled() const { if (m_cancel_status) throw CanceledException(); } Model m_model; private: tbb::atomic m_cancel_status; // Callback to be evoked regularly to update state of the UI thread. status_callback_type m_status_callback; // Callback to be evoked to stop the background processing before a state is updated. cancel_callback_type m_cancel_callback = [](){}; // Mutex used for synchronization of the worker thread with the UI thread: // The mutex will be used to guard the worker thread against entering a stage // while the data influencing the stage is modified. mutable tbb::mutex m_state_mutex; }; template class PrintBaseWithState : public PrintBase { public: bool is_step_done(PrintStepEnum step) const { return m_state.is_done(step, this->state_mutex()); } PrintStateBase::StateWithTimeStamp step_state_with_timestamp(PrintStepEnum step) const { return m_state.state_with_timestamp(step, this->state_mutex()); } protected: bool set_started(PrintStepEnum step) { return m_state.set_started(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); } PrintStateBase::TimeStamp set_done(PrintStepEnum step) { return m_state.set_done(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); } bool invalidate_step(PrintStepEnum step) { return m_state.invalidate(step, this->cancel_callback()); } template bool invalidate_steps(StepTypeIterator step_begin, StepTypeIterator step_end) { return m_state.invalidate_multiple(step_begin, step_end, this->cancel_callback()); } bool invalidate_steps(std::initializer_list il) { return m_state.invalidate_multiple(il.begin(), il.end(), this->cancel_callback()); } bool invalidate_all_steps() { return m_state.invalidate_all(this->cancel_callback()); } private: PrintState m_state; }; template class PrintObjectBaseWithState : public PrintObjectBase { public: PrintType* print() { return m_print; } const PrintType* print() const { return m_print; } typedef PrintState PrintObjectState; bool is_step_done(PrintObjectStepEnum step) const { return m_state.is_done(step, PrintObjectBase::state_mutex(m_print)); } PrintStateBase::StateWithTimeStamp step_state_with_timestamp(PrintObjectStepEnum step) const { return m_state.state_with_timestamp(step, PrintObjectBase::state_mutex(m_print)); } protected: PrintObjectBaseWithState(PrintType *print, ModelObject *model_object) : PrintObjectBase(model_object), m_print(print) {} bool set_started(PrintObjectStepEnum step) { return m_state.set_started(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); } PrintStateBase::TimeStamp set_done(PrintObjectStepEnum step) { return m_state.set_done(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); } bool invalidate_step(PrintObjectStepEnum step) { return m_state.invalidate(step, PrintObjectBase::cancel_callback(m_print)); } template bool invalidate_steps(StepTypeIterator step_begin, StepTypeIterator step_end) { return m_state.invalidate_multiple(step_begin, step_end, PrintObjectBase::cancel_callback(m_print)); } bool invalidate_steps(std::initializer_list il) { return m_state.invalidate_multiple(il.begin(), il.end(), PrintObjectBase::cancel_callback(m_print)); } bool invalidate_all_steps() { return m_state.invalidate_all(PrintObjectBase::cancel_callback(m_print)); } protected: // If the background processing stop was requested, throw CanceledException. // To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly. void throw_if_canceled() { if (m_print->canceled()) throw CanceledException(); } friend PrintType; PrintType *m_print; private: PrintState m_state; }; } // namespace Slic3r #endif /* slic3r_PrintBase_hpp_ */