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 31fbfa56de
where the PrintObject shared data invalidation condition was flipped.
This commit is contained in:
parent
dac1e60153
commit
661463645b
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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<PrintObject>(const_cast<const PrintObject* const* const>(&(*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();
|
||||
}
|
||||
}
|
||||
|
@ -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<int>(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<typename ThrowIfCanceled>
|
||||
std::pair<TimeStamp, bool> set_done(StepType step, std::mutex &mtx, ThrowIfCanceled throw_if_canceled) {
|
||||
std::scoped_lock<std::mutex> 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<int>(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<typename CancelationCallback>
|
||||
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<typename CancelationCallback, typename StepTypeIterator>
|
||||
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<typename CancelationCallback>
|
||||
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<std::mutex> 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<StepType, bool> retval(static_cast<StepType>(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<typename PrintObject>
|
||||
void finalize_impl(std::vector<PrintObject*> &print_objects)
|
||||
{
|
||||
// Grab the lock for the Print / PrintObject milestones.
|
||||
std::scoped_lock<std::mutex> 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().
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user