From 661463645b3e260fd94648ec2e1ee2ea75db7435 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 12 Jan 2023 18:04:59 +0100 Subject: [PATCH] Background processing: The milestone state machine was extended with canceled / invalidated states. Print / PrintObject infrastructure was extended with a cleanup() callback, which may check for the new State::Canceled / State::Invalid states of a particular milestone and turn it to State::Fresh while releasing data of that particular milestone which is no more valid. Also fixed a bug in 31fbfa56de70bf8093cea5fe56c73b0e6fa017c3 where the PrintObject shared data invalidation condition was flipped. --- src/libslic3r/GCode.cpp | 2 +- src/libslic3r/Print.hpp | 4 ++ src/libslic3r/PrintApply.cpp | 6 +- src/libslic3r/PrintBase.hpp | 129 ++++++++++++++++++++++++---------- src/libslic3r/PrintObject.cpp | 15 ++++ src/slic3r/GUI/GLCanvas3D.cpp | 10 +-- 6 files changed, 119 insertions(+), 47 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index b9817776f..1e152c35a 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -740,7 +740,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu // Does the file exist? If so, we hope that it is still valid. { PrintStateBase::StateWithTimeStamp state = print->step_state_with_timestamp(psGCodeExport); - if (! state.enabled || (state.state == PrintStateBase::DONE && boost::filesystem::exists(boost::filesystem::path(path)))) + if (! state.enabled || (state.is_done() && boost::filesystem::exists(boost::filesystem::path(path)))) return; } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 31e78d714..7297cdfd5 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -358,11 +358,15 @@ private: // If ! m_slicing_params.valid, recalculate. void update_slicing_parameters(); + // Called on main thread with stopped or paused background processing to let PrintObject release data for its milestones that were invalidated or canceled. + void cleanup(); + static PrintObjectConfig object_config_from_model_object(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders); private: void make_perimeters(); void prepare_infill(); + void clear_fills(); void infill(); void ironing(); void generate_support_spots(); diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 5f95edefa..4f586a17c 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1470,9 +1470,11 @@ void Print::cleanup() for (auto it = all_objects.begin(); it != all_objects.end();) { PrintObjectRegions *shared_regions = (*it)->m_shared_regions; auto it_begin = it; - for (++ it; it != all_objects.end() && shared_regions == (*it)->shared_regions(); ++ it); + for (; it != all_objects.end() && shared_regions == (*it)->shared_regions(); ++ it) + // Let the PrintObject clean up its data with invalidated milestones. + (*it)->cleanup(); auto this_objects = SpanOfConstPtrs(const_cast(&(*it_begin)), it - it_begin); - if (Print::is_shared_print_object_step_valid_unguarded(this_objects, posSupportSpotsSearch)) + if (! Print::is_shared_print_object_step_valid_unguarded(this_objects, posSupportSpotsSearch)) shared_regions->generated_support_points.reset(); } } diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index d218146ac..607a7b5a5 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -23,10 +23,29 @@ public: class PrintStateBase { public: - enum State { - INVALID, - STARTED, - DONE, + enum class State { + // Fresh state, either the object is new or the data of that particular milestone was cleaned up. + // Fresh state may transit to Started. + Fresh, + // Milestone was started and now it is being executed. + // Started state may transit to Canceled with invalid data or Done with valid data. + Started, + // Milestone was being executed, but now it is canceled and not yet cleaned up. + // Canceled state may transit to Fresh state if its invalid data is cleaned up + // or to Started state. + // Canceled and Invalidated states are of similar nature: Canceled step was Started but canceled, + // while Invalidated state was Done but invalidated. + Canceled, + // Milestone was finished successfully, it's data is now valid. + // Done state may transit to Invalidated state if its data is no more valid + // or to a Started state. + Done, + // Milestone was finished successfully (done), but now it is invalidated and it's data is no more valid. + // Invalidated state may transit to Fresh if its invalid data is cleaned up, + // or to state Started. + // Canceled and Invalidated states are of similar nature: Canceled step was Started but canceled, + // while Invalidated state was Done but invalidated. + Invalidated, }; enum class WarningLevel { @@ -39,9 +58,25 @@ public: // A new unique timestamp is being assigned to the step every time the step changes its state. struct StateWithTimeStamp { - State state { INVALID }; + State state { State::Fresh }; TimeStamp timestamp { 0 }; bool enabled { true }; + + bool is_done() const { return state == State::Done; } + // The milestone may have some data available, but it is no more valid and it should be cleaned up to conserve memory. + bool is_dirty() const { return state == State::Canceled || state == State::Invalidated; } + + // If the milestone is Started or Done, invalidate it: + // Turn Started to Canceled, turn Done to Invalidated. + // Update timestamp of this milestone. + bool try_invalidate() { + bool invalidated = this->state == State::Started || this->state == State::Done; + if (invalidated) { + this->state = this->state == State::Started ? State::Canceled : State::Invalidated; + this->timestamp = ++ g_last_timestamp; + } + return invalidated; + } }; struct Warning @@ -93,11 +128,11 @@ public: } bool is_started(StepType step, std::mutex &mtx) const { - return this->state_with_timestamp(step, mtx).state == STARTED; + return this->state_with_timestamp(step, mtx).state == State::Started; } bool is_done(StepType step, std::mutex &mtx) const { - return this->state_with_timestamp(step, mtx).state == DONE; + return this->state_with_timestamp(step, mtx).state == State::Done; } StateWithTimeStamp state_with_timestamp_unguarded(StepType step) const { @@ -105,11 +140,11 @@ public: } bool is_started_unguarded(StepType step) const { - return this->state_with_timestamp_unguarded(step).state == STARTED; + return this->state_with_timestamp_unguarded(step).state == State::Started; } bool is_done_unguarded(StepType step) const { - return this->state_with_timestamp_unguarded(step).state == DONE; + return this->state_with_timestamp_unguarded(step).state == State::Done; } void enable_unguarded(StepType step, bool enable) { @@ -146,12 +181,12 @@ public: // // assert(m_step_active == -1); // for (int i = 0; i < int(COUNT); ++ i) -// assert(m_state[i].state != STARTED); +// assert(m_state[i].state != State::Started); #endif // NDEBUG PrintStateBase::StateWithWarnings &state = m_state[step]; - if (! state.enabled || state.state == DONE) + if (! state.enabled || state.state == State::Done) return false; - state.state = STARTED; + state.state = State::Started; state.timestamp = ++ g_last_timestamp; state.mark_warnings_non_current(); m_step_active = static_cast(step); @@ -161,17 +196,17 @@ public: // Set the step as done. Block on mutex while the Print / PrintObject / PrintRegion objects are being // modified by the UI thread. // Return value: - // Timestamp when this stepentered the DONE state. + // Timestamp when this step entered the Done state. // bool indicates whether the UI has to update the slicing warnings of this step or not. template std::pair set_done(StepType step, std::mutex &mtx, ThrowIfCanceled throw_if_canceled) { std::scoped_lock lock(mtx); // If canceled, throw before changing the step state. throw_if_canceled(); - assert(m_state[step].state == STARTED); + assert(m_state[step].state == State::Started); assert(m_step_active == static_cast(step)); PrintStateBase::StateWithWarnings &state = m_state[step]; - state.state = DONE; + state.state = State::Done; state.timestamp = ++ g_last_timestamp; m_step_active = -1; // Remove all non-current warnings. @@ -190,16 +225,12 @@ public: // processing by calling the cancel callback. template bool invalidate(StepType step, CancelationCallback cancel) { - bool invalidated = m_state[step].state != INVALID; - if (invalidated) { + if (PrintStateBase::StateWithWarnings &state = m_state[step]; state.try_invalidate()) { #if 0 if (mtx.state != mtx.HELD) { printf("Not held!\n"); } #endif - PrintStateBase::StateWithWarnings &state = m_state[step]; - state.state = INVALID; - state.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 @@ -209,21 +240,17 @@ public: // It is safe to modify it. state.mark_warnings_non_current(); m_step_active = -1; - } - return invalidated; + return true; + } else + return false; } 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) { + for (StepTypeIterator it = step_begin; it != step_end; ++ it) + if (m_state[*it].try_invalidate()) invalidated = true; - state.state = INVALID; - state.timestamp = ++ g_last_timestamp; - } - } if (invalidated) { #if 0 if (mtx.state != mtx.HELD) { @@ -251,14 +278,9 @@ public: 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) { + for (size_t i = 0; i < COUNT; ++ i) + if (m_state[i].try_invalidate()) invalidated = true; - state.state = INVALID; - state.timestamp = ++ g_last_timestamp; - } - } if (invalidated) { cancel(); // Now the worker thread should be stopped, therefore it cannot write into the warnings field. @@ -270,6 +292,26 @@ public: return invalidated; } + // If the milestone is Canceled or Invalidated, return true and turn the state of the milestone to Fresh. + // The caller is responsible for releasing the data of the milestone that is no more valid. + bool query_reset_dirty_unguarded(StepType step) { + if (PrintStateBase::StateWithWarnings &state = m_state[step]; state.is_dirty()) { + state.state = State::Fresh; + return true; + } else + return false; + } + + // To be called after the background thread was stopped by the user pressing the Cancel button, + // which in turn stops the background thread without adjusting state of the milestone being executed. + // This method fixes the state of the canceled milestone by setting it to a Canceled state. + void mark_canceled_unguarded() { + for (size_t i = 0; i < COUNT; ++ i) { + if (State &state = m_state[i].state; state == State::Started) + state = State::Canceled; + } + } + // Update list of warnings of the current milestone with a new warning. // The warning may already exist in the list, marked as current or not current. // If it already exists, mark it as current. @@ -281,7 +323,7 @@ public: std::scoped_lock lock(mtx); assert(m_step_active != -1); StateWithWarnings &state = m_state[m_step_active]; - assert(state.state == STARTED); + assert(state.state == State::Started); std::pair retval(static_cast(m_step_active), true); // Does a warning of the same level and message or message_id exist already? auto it = (message_id == 0) ? @@ -664,15 +706,19 @@ protected: } // Clean up after process() finished, either with success, error or if canceled. - // The adjustments on the Print / PrintObject m_stepmask data due to set_task() are to be reverted here. + // The adjustments on the Print / PrintObject m_stepmask data due to set_task() are to be reverted here: + // Execution of all milestones is enabled in case some of them were suppressed for the last background execution. + // Also if the background processing was canceled, the current milestone that was just abandoned + // in Started state is to be reset to Canceled state. template void finalize_impl(std::vector &print_objects) { // Grab the lock for the Print / PrintObject milestones. std::scoped_lock lock(this->state_mutex()); for (auto *po : print_objects) - po->enable_all_steps_unguarded(true); + po->finalize_impl(); m_state.enable_all_unguarded(true); + m_state.mark_canceled_unguarded(); } private: @@ -722,6 +768,11 @@ protected: bool is_step_enabled_unguarded(PrintObjectStepEnum step) const { return m_state.is_enabled_unguarded(step); } void enable_step_unguarded(PrintObjectStepEnum step, bool enable) { m_state.enable_unguarded(step, enable); } void enable_all_steps_unguarded(bool enable) { m_state.enable_all_unguarded(enable); } + // See the comment at PrintBaseWithState::finalize_impl() + void finalize_impl() { m_state.enable_all_unguarded(true); m_state.mark_canceled_unguarded(); } + // If the milestone is Canceled or Invalidated, return true and turn the state of the milestone to Fresh. + // The caller is responsible for releasing the data of the milestone that is no more valid. + bool query_reset_dirty_step_unguarded(PrintObjectStepEnum step) { return m_state.query_reset_dirty_unguarded(step); } // Add a slicing warning to the active PrintObject step and send a status notification. // This method could be called multiple times between this->set_started() and this->set_done(). diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 5423a3ad1..42bb6bf31 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -352,6 +352,12 @@ void PrintObject::prepare_infill() this->set_done(posPrepareInfill); } +void PrintObject::clear_fills() +{ + for (Layer *layer : m_layers) + layer->clear_fills(); +} + void PrintObject::infill() { // prerequisites @@ -818,6 +824,15 @@ bool PrintObject::invalidate_all_steps() return result; } +// Called on main thread with stopped or paused background processing to let PrintObject release data for its milestones that were invalidated or canceled. +void PrintObject::cleanup() +{ + if (this->query_reset_dirty_step_unguarded(posInfill)) + this->clear_fills(); + if (this->query_reset_dirty_step_unguarded(posSupportMaterial)) + this->clear_support_layers(); +} + // This function analyzes slices of a region (SurfaceCollection slices). // Each region slice (instance of Surface) is analyzed, whether it is supported or whether it is the top surface. // Initially all slices are of type stInternal. diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 453035024..5331094c1 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1867,11 +1867,11 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re SLASupportState state; for (size_t istep = 0; istep < sla_steps.size(); ++istep) { state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]); - if (state.step[istep].state == PrintStateBase::DONE) { + if (state.step[istep].is_done()) { if (!print_object->has_mesh(sla_steps[istep])) - // Consider the DONE step without a valid mesh as invalid for the purpose + // Consider the Done step without a valid mesh as invalid for the purpose // of mesh visualization. - state.step[istep].state = PrintStateBase::INVALID; + state.step[istep].state = PrintStateBase::State::Fresh; else if (sla_steps[istep] != slaposDrillHoles) for (const ModelInstance* model_instance : print_object->model_object()->instances) // Only the instances, which are currently printable, will have the SLA support structures kept. @@ -2038,7 +2038,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re if (! volume.offsets.empty() && state.step[istep].timestamp != volume.offsets.front()) { // The backend either produced a new hollowed mesh, or it invalidated the one that the front end has seen. volume.model.reset(); - if (state.step[istep].state == PrintStateBase::DONE) { + if (state.step[istep].is_done()) { TriangleMesh mesh = print_object->get_mesh(slaposDrillHoles); assert(! mesh.empty()); @@ -2071,7 +2071,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // of various concenrs (model vs. 3D print path). volume.offsets = { state.step[istep].timestamp }; } - else if (state.step[istep].state == PrintStateBase::DONE) { + else if (state.step[istep].is_done()) { // Check whether there is an existing auxiliary volume to be updated, or a new auxiliary volume to be created. ModelVolumeState key(state.step[istep].timestamp, instance.instance_id.id); auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower);