WIP: Undo / Redo memory limiting by releasing the least recently

used snapshots. Memory limit set to 10% of physical system memory.
This commit is contained in:
bubnikv 2019-07-17 15:48:53 +02:00
parent 4865240a9c
commit 3a74e7ab69
12 changed files with 411 additions and 82 deletions

View file

@ -127,6 +127,10 @@ struct stl_file {
this->stats.reset(); this->stats.reset();
} }
size_t memsize() const {
return sizeof(*this) + sizeof(stl_facet) * facet_start.size() + sizeof(stl_neighbors) * neighbors_start.size();
}
std::vector<stl_facet> facet_start; std::vector<stl_facet> facet_start;
std::vector<stl_neighbors> neighbors_start; std::vector<stl_neighbors> neighbors_start;
// Statistics // Statistics
@ -139,6 +143,10 @@ struct indexed_triangle_set
void clear() { indices.clear(); vertices.clear(); } void clear() { indices.clear(); vertices.clear(); }
size_t memsize() const {
return sizeof(*this) + sizeof(stl_triangle_vertex_indices) * indices.size() + sizeof(stl_vertex) * vertices.size();
}
std::vector<stl_triangle_vertex_indices> indices; std::vector<stl_triangle_vertex_indices> indices;
std::vector<stl_vertex> vertices; std::vector<stl_vertex> vertices;
//FIXME add normals once we get rid of the stl_file from TriangleMesh completely. //FIXME add normals once we get rid of the stl_file from TriangleMesh completely.

View file

@ -607,6 +607,12 @@ void TriangleMesh::require_shared_vertices()
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end"; BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end";
} }
size_t TriangleMesh::memsize() const
{
size_t memsize = 8 + this->stl.memsize() + this->its.memsize();
return memsize;
}
void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callback_type throw_on_cancel) void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callback_type throw_on_cancel)
{ {
mesh = _mesh; mesh = _mesh;

View file

@ -67,6 +67,8 @@ public:
size_t facets_count() const { return this->stl.stats.number_of_facets; } size_t facets_count() const { return this->stl.stats.number_of_facets; }
bool empty() const { return this->facets_count() == 0; } bool empty() const { return this->facets_count() == 0; }
bool is_splittable() const; bool is_splittable() const;
// Estimate of the memory occupied by this structure.
size_t memsize() const;
stl_file stl; stl_file stl;
indexed_triangle_set its; indexed_triangle_set its;

View file

@ -21,6 +21,8 @@ extern std::string format_memsize_MB(size_t n);
// The string is non-empty only if the loglevel >= info (3). // The string is non-empty only if the loglevel >= info (3).
extern std::string log_memory_info(); extern std::string log_memory_info();
extern void disable_multi_threading(); extern void disable_multi_threading();
// Returns the size of physical memory (RAM) in bytes.
extern size_t total_physical_memory();
// Set a path with GUI resource files. // Set a path with GUI resource files.
void set_var_dir(const std::string &path); void set_var_dir(const std::string &path);

View file

@ -7,10 +7,15 @@
#include <stdio.h> #include <stdio.h>
#ifdef WIN32 #ifdef WIN32
#include <windows.h> #include <windows.h>
#include <psapi.h> #include <psapi.h>
#else #else
#include <unistd.h> #include <unistd.h>
#include <sys/types.h>
#include <sys/param.h>
#ifdef BSD
#include <sys/sysctl.h>
#endif
#endif #endif
#include <boost/log/core.hpp> #include <boost/log/core.hpp>
@ -467,4 +472,75 @@ std::string log_memory_info()
} }
#endif #endif
// Returns the size of physical memory (RAM) in bytes.
// http://nadeausoftware.com/articles/2012/09/c_c_tip_how_get_physical_memory_size_system
size_t total_physical_memory()
{
#if defined(_WIN32) && (defined(__CYGWIN__) || defined(__CYGWIN32__))
// Cygwin under Windows. ------------------------------------
// New 64-bit MEMORYSTATUSEX isn't available. Use old 32.bit
MEMORYSTATUS status;
status.dwLength = sizeof(status);
GlobalMemoryStatus( &status );
return (size_t)status.dwTotalPhys;
#elif defined(_WIN32)
// Windows. -------------------------------------------------
// Use new 64-bit MEMORYSTATUSEX, not old 32-bit MEMORYSTATUS
MEMORYSTATUSEX status;
status.dwLength = sizeof(status);
GlobalMemoryStatusEx( &status );
return (size_t)status.ullTotalPhys;
#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
// UNIX variants. -------------------------------------------
// Prefer sysctl() over sysconf() except sysctl() HW_REALMEM and HW_PHYSMEM
#if defined(CTL_HW) && (defined(HW_MEMSIZE) || defined(HW_PHYSMEM64))
int mib[2];
mib[0] = CTL_HW;
#if defined(HW_MEMSIZE)
mib[1] = HW_MEMSIZE; // OSX. ---------------------
#elif defined(HW_PHYSMEM64)
mib[1] = HW_PHYSMEM64; // NetBSD, OpenBSD. ---------
#endif
int64_t size = 0; // 64-bit
size_t len = sizeof( size );
if ( sysctl( mib, 2, &size, &len, NULL, 0 ) == 0 )
return (size_t)size;
return 0L; // Failed?
#elif defined(_SC_AIX_REALMEM)
// AIX. -----------------------------------------------------
return (size_t)sysconf( _SC_AIX_REALMEM ) * (size_t)1024L;
#elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE)
// FreeBSD, Linux, OpenBSD, and Solaris. --------------------
return (size_t)sysconf( _SC_PHYS_PAGES ) *
(size_t)sysconf( _SC_PAGESIZE );
#elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGE_SIZE)
// Legacy. --------------------------------------------------
return (size_t)sysconf( _SC_PHYS_PAGES ) *
(size_t)sysconf( _SC_PAGE_SIZE );
#elif defined(CTL_HW) && (defined(HW_PHYSMEM) || defined(HW_REALMEM))
// DragonFly BSD, FreeBSD, NetBSD, OpenBSD, and OSX. --------
int mib[2];
mib[0] = CTL_HW;
#if defined(HW_REALMEM)
mib[1] = HW_REALMEM; // FreeBSD. -----------------
#elif defined(HW_PYSMEM)
mib[1] = HW_PHYSMEM; // Others. ------------------
#endif
unsigned int size = 0; // 32-bit
size_t len = sizeof( size );
if ( sysctl( mib, 2, &size, &len, NULL, 0 ) == 0 )
return (size_t)size;
return 0L; // Failed?
#endif // sysctl and sysconf variants
#else
return 0L; // Unknown OS.
#endif
}
}; // namespace Slic3r }; // namespace Slic3r

View file

@ -534,7 +534,7 @@ void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_max
}); });
} }
void GUI_App::load_project(wxWindow *parent, wxString& input_file) void GUI_App::load_project(wxWindow *parent, wxString& input_file) const
{ {
input_file.Clear(); input_file.Clear();
wxFileDialog dialog(parent ? parent : GetTopWindow(), wxFileDialog dialog(parent ? parent : GetTopWindow(),
@ -546,7 +546,7 @@ void GUI_App::load_project(wxWindow *parent, wxString& input_file)
input_file = dialog.GetPath(); input_file = dialog.GetPath();
} }
void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const
{ {
input_files.Clear(); input_files.Clear();
wxFileDialog dialog(parent ? parent : GetTopWindow(), wxFileDialog dialog(parent ? parent : GetTopWindow(),

View file

@ -121,8 +121,8 @@ public:
void recreate_GUI(); void recreate_GUI();
void system_info(); void system_info();
void keyboard_shortcuts(); void keyboard_shortcuts();
void load_project(wxWindow *parent, wxString& input_file); void load_project(wxWindow *parent, wxString& input_file) const;
void import_model(wxWindow *parent, wxArrayString& input_files); void import_model(wxWindow *parent, wxArrayString& input_files) const;
static bool catch_error(std::function<void()> cb, const std::string& err); static bool catch_error(std::function<void()> cb, const std::string& err);
void persist_window_geometry(wxTopLevelWindow *window, bool default_maximized = false); void persist_window_geometry(wxTopLevelWindow *window, bool default_maximized = false);

View file

@ -71,9 +71,6 @@ static void take_snapshot(const wxString& snapshot_name)
wxGetApp().plater()->take_snapshot(snapshot_name); wxGetApp().plater()->take_snapshot(snapshot_name);
} }
static void suppress_snapshots(){ wxGetApp().plater()->suppress_snapshots(); }
static void allow_snapshots() { wxGetApp().plater()->allow_snapshots(); }
ObjectList::ObjectList(wxWindow* parent) : ObjectList::ObjectList(wxWindow* parent) :
wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE), wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE),
m_parent(parent) m_parent(parent)
@ -2406,8 +2403,7 @@ void ObjectList::remove()
wxDataViewItem parent = wxDataViewItem(0); wxDataViewItem parent = wxDataViewItem(0);
take_snapshot(_(L("Delete Selected"))); Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete Selected")));
suppress_snapshots();
for (auto& item : sels) for (auto& item : sels)
{ {
@ -2429,8 +2425,6 @@ void ObjectList::remove()
if (parent) if (parent)
select_item(parent); select_item(parent);
allow_snapshots();
} }
void ObjectList::del_layer_range(const t_layer_height_range& range) void ObjectList::del_layer_range(const t_layer_height_range& range)
@ -2505,8 +2499,7 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range& curre
t_layer_height_range new_range = { midl_layer, next_range.second }; t_layer_height_range new_range = { midl_layer, next_range.second };
take_snapshot(_(L("Add New Layers Range"))); Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add New Layers Range")));
suppress_snapshots();
// create new 2 layers instead of deleted one // create new 2 layers instead of deleted one
@ -2521,7 +2514,6 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range& curre
new_range = { current_range.second, midl_layer }; new_range = { current_range.second, midl_layer };
ranges[new_range] = get_default_layer_config(obj_idx); ranges[new_range] = get_default_layer_config(obj_idx);
add_layer_item(new_range, layers_item, layer_idx); add_layer_item(new_range, layers_item, layer_idx);
allow_snapshots();
} }
else else
{ {
@ -3531,7 +3523,7 @@ void ObjectList::update_after_undo_redo()
m_prevent_list_events = true; m_prevent_list_events = true;
m_prevent_canvas_selection_update = true; m_prevent_canvas_selection_update = true;
suppress_snapshots(); Plater::SuppressSnapshots suppress(wxGetApp().plater());
// Unselect all objects before deleting them, so that no change of selection is emitted during deletion. // Unselect all objects before deleting them, so that no change of selection is emitted during deletion.
this->UnselectAll(); this->UnselectAll();
@ -3543,8 +3535,6 @@ void ObjectList::update_after_undo_redo()
++obj_idx; ++obj_idx;
} }
allow_snapshots();
#ifndef __WXOSX__ #ifndef __WXOSX__
selection_changed(); selection_changed();
#endif /* __WXOSX__ */ #endif /* __WXOSX__ */

View file

@ -1255,13 +1255,9 @@ const std::regex PlaterDropTarget::pattern_drop(".*[.](stl|obj|amf|3mf|prusa)",
bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames) bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames)
{ {
plater->take_snapshot(_(L("Load Files")));
std::vector<fs::path> paths; std::vector<fs::path> paths;
for (const auto &filename : filenames) { for (const auto &filename : filenames) {
fs::path path(into_path(filename)); fs::path path(into_path(filename));
if (std::regex_match(path.string(), pattern_drop)) { if (std::regex_match(path.string(), pattern_drop)) {
paths.push_back(std::move(path)); paths.push_back(std::move(path));
} else { } else {
@ -1269,6 +1265,23 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi
} }
} }
wxString snapshot_label;
assert(! paths.empty());
if (paths.size() == 1) {
snapshot_label = _(L("Load File"));
snapshot_label += ": ";
snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
} else {
snapshot_label = _(L("Load Files"));
snapshot_label += ": ";
snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
for (size_t i = 1; i < paths.size(); ++ i) {
snapshot_label += ", ";
snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str());
}
}
Plater::TakeSnapshot snapshot(plater, snapshot_label);
// FIXME: when drag and drop is done on a .3mf or a .amf file we should clear the plater for consistence with the open project command // FIXME: when drag and drop is done on a .3mf or a .amf file we should clear the plater for consistence with the open project command
// (the following call to plater->load_files() will load the config data, if present) // (the following call to plater->load_files() will load the config data, if present)
@ -1595,7 +1608,7 @@ struct Plater::priv
priv(Plater *q, MainFrame *main_frame); priv(Plater *q, MainFrame *main_frame);
~priv(); ~priv();
void update(bool force_full_scene_refresh = false); void update(bool force_full_scene_refresh = false, bool force_background_processing_update = false);
void select_view(const std::string& direction); void select_view(const std::string& direction);
void select_view_3D(const std::string& name); void select_view_3D(const std::string& name);
void select_next_view_3D(); void select_next_view_3D();
@ -1634,6 +1647,8 @@ struct Plater::priv
return; return;
assert(this->m_prevent_snapshots >= 0); assert(this->m_prevent_snapshots >= 0);
this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection()); this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection());
this->undo_redo_stack.release_least_recently_used();
BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack.memsize()) << log_memory_info();
} }
void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); } void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); }
int get_active_snapshot_index(); int get_active_snapshot_index();
@ -1734,7 +1749,7 @@ private:
void update_fff_scene(); void update_fff_scene();
void update_sla_scene(); void update_sla_scene();
void update_after_undo_redo(); void update_after_undo_redo(bool temp_snapshot_was_taken = false);
// path to project file stored with no extension // path to project file stored with no extension
wxString m_project_filename; wxString m_project_filename;
@ -1888,7 +1903,7 @@ Plater::priv::~priv()
delete config; delete config;
} }
void Plater::priv::update(bool force_full_scene_refresh) void Plater::priv::update(bool force_full_scene_refresh, bool force_background_processing_update)
{ {
// the following line, when enabled, causes flickering on NVIDIA graphics cards // the following line, when enabled, causes flickering on NVIDIA graphics cards
// wxWindowUpdateLocker freeze_guard(q); // wxWindowUpdateLocker freeze_guard(q);
@ -1901,7 +1916,7 @@ void Plater::priv::update(bool force_full_scene_refresh)
} }
unsigned int update_status = 0; unsigned int update_status = 0;
if (this->printer_technology == ptSLA) if (this->printer_technology == ptSLA || force_background_processing_update)
// Update the SLAPrint from the current Model, so that the reload_scene() // Update the SLAPrint from the current Model, so that the reload_scene()
// pulls the correct data. // pulls the correct data.
update_status = this->update_background_process(false); update_status = this->update_background_process(false);
@ -2459,7 +2474,10 @@ void Plater::priv::remove(size_t obj_idx)
void Plater::priv::delete_object_from_model(size_t obj_idx) void Plater::priv::delete_object_from_model(size_t obj_idx)
{ {
this->take_snapshot(_(L("Delete Object"))); wxString snapshot_label = _(L("Delete Object"));
if (! model.objects[obj_idx]->name.empty())
snapshot_label += ": " + wxString::FromUTF8(model.objects[obj_idx]->name.c_str());
Plater::TakeSnapshot snapshot(q, snapshot_label);
model.delete_object(obj_idx); model.delete_object(obj_idx);
update(); update();
object_list_changed(); object_list_changed();
@ -2467,7 +2485,7 @@ void Plater::priv::delete_object_from_model(size_t obj_idx)
void Plater::priv::reset() void Plater::priv::reset()
{ {
this->take_snapshot(_(L("Reset Project"))); Plater::TakeSnapshot snapshot(q, _(L("Reset Project")));
set_project_filename(wxEmptyString); set_project_filename(wxEmptyString);
@ -2663,7 +2681,7 @@ void Plater::priv::split_object()
Slic3r::GUI::warning_catcher(q, _(L("The selected object couldn't be split because it contains only one part."))); Slic3r::GUI::warning_catcher(q, _(L("The selected object couldn't be split because it contains only one part.")));
else else
{ {
this->take_snapshot(_(L("Split to Objects"))); Plater::TakeSnapshot snapshot(q, _(L("Split to Objects")));
unsigned int counter = 1; unsigned int counter = 1;
for (ModelObject* m : new_objects) for (ModelObject* m : new_objects)
@ -2903,7 +2921,7 @@ void Plater::priv::update_sla_scene()
void Plater::priv::reload_from_disk() void Plater::priv::reload_from_disk()
{ {
this->take_snapshot(_(L("Reload from Disk"))); Plater::TakeSnapshot snapshot(q, _(L("Reload from Disk")));
const auto &selection = get_selection(); const auto &selection = get_selection();
const auto obj_orig_idx = selection.get_object_idx(); const auto obj_orig_idx = selection.get_object_idx();
@ -2939,7 +2957,7 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* =
if (obj_idx < 0) if (obj_idx < 0)
return; return;
this->take_snapshot(_(L("Fix Throught NetFabb"))); Plater::TakeSnapshot snapshot(q, _(L("Fix Throught NetFabb")));
fix_model_by_win10_sdk_gui(*model.objects[obj_idx], vol_idx); fix_model_by_win10_sdk_gui(*model.objects[obj_idx], vol_idx);
this->update(); this->update();
@ -3603,7 +3621,7 @@ void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const
int Plater::priv::get_active_snapshot_index() int Plater::priv::get_active_snapshot_index()
{ {
const size_t& active_snapshot_time = this->undo_redo_stack.active_snapshot_time(); const size_t active_snapshot_time = this->undo_redo_stack.active_snapshot_time();
const std::vector<UndoRedo::Snapshot>& ss_stack = this->undo_redo_stack.snapshots(); const std::vector<UndoRedo::Snapshot>& ss_stack = this->undo_redo_stack.snapshots();
const auto it = std::lower_bound(ss_stack.begin(), ss_stack.end(), UndoRedo::Snapshot(active_snapshot_time)); const auto it = std::lower_bound(ss_stack.begin(), ss_stack.end(), UndoRedo::Snapshot(active_snapshot_time));
return it - ss_stack.begin(); return it - ss_stack.begin();
@ -3611,8 +3629,9 @@ int Plater::priv::get_active_snapshot_index()
void Plater::priv::undo() void Plater::priv::undo()
{ {
bool temp_snapshot_was_taken = this->undo_redo_stack.temp_snapshot_active();
if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection())) if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection()))
this->update_after_undo_redo(); this->update_after_undo_redo(temp_snapshot_was_taken);
} }
void Plater::priv::redo() void Plater::priv::redo()
@ -3623,8 +3642,9 @@ void Plater::priv::redo()
void Plater::priv::undo_to(size_t time_to_load) void Plater::priv::undo_to(size_t time_to_load)
{ {
bool temp_snapshot_was_taken = this->undo_redo_stack.temp_snapshot_active();
if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), time_to_load)) if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), time_to_load))
this->update_after_undo_redo(); this->update_after_undo_redo(temp_snapshot_was_taken);
} }
void Plater::priv::redo_to(size_t time_to_load) void Plater::priv::redo_to(size_t time_to_load)
@ -3633,10 +3653,16 @@ void Plater::priv::redo_to(size_t time_to_load)
this->update_after_undo_redo(); this->update_after_undo_redo();
} }
void Plater::priv::update_after_undo_redo() void Plater::priv::update_after_undo_redo(bool /* temp_snapshot_was_taken */)
{ {
this->view3D->get_canvas3d()->get_selection().clear(); this->view3D->get_canvas3d()->get_selection().clear();
this->update(false); // update volumes from the deserializd model // Update volumes from the deserializd model, always stop / update the background processing (for both the SLA and FFF technologies).
this->update(false, true);
// Release old snapshots if the memory allocated is excessive. This may remove the top most snapshot if jumping to the very first snapshot.
//if (temp_snapshot_was_taken)
// Release the old snapshots always, as it may have happened, that some of the triangle meshes got deserialized from the snapshot, while some
// triangle meshes may have gotten released from the scene or the background processing, therefore now being calculated into the Undo / Redo stack size.
this->undo_redo_stack.release_least_recently_used();
//YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) (no selections at this point of time) //YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) (no selections at this point of time)
this->view3D->get_canvas3d()->get_selection().set_deserialized(GUI::Selection::EMode(this->undo_redo_stack.selection_deserialized().mode), this->undo_redo_stack.selection_deserialized().volumes_and_instances); this->view3D->get_canvas3d()->get_selection().set_deserialized(GUI::Selection::EMode(this->undo_redo_stack.selection_deserialized().mode), this->undo_redo_stack.selection_deserialized().volumes_and_instances);
@ -3644,6 +3670,8 @@ void Plater::priv::update_after_undo_redo()
//FIXME what about the state of the manipulators? //FIXME what about the state of the manipulators?
//FIXME what about the focus? Cursor in the side panel? //FIXME what about the focus? Cursor in the side panel?
BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot reloaded. Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack.memsize()) << log_memory_info();
} }
void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const
@ -3683,10 +3711,12 @@ void Plater::new_project()
void Plater::load_project() void Plater::load_project()
{ {
this->take_snapshot(_(L("Load Project"))); // Ask user for a project file name.
wxString input_file; wxString input_file;
wxGetApp().load_project(this, input_file); wxGetApp().load_project(this, input_file);
// Take the Undo / Redo snapshot.
Plater::TakeSnapshot snapshot(this, _(L("Load Project")) + ": " + wxString::FromUTF8(into_path(input_file).stem().string().c_str()));
// And finally load the new project.
load_project(input_file); load_project(input_file);
} }
@ -3710,13 +3740,28 @@ void Plater::add_model()
if (input_files.empty()) if (input_files.empty())
return; return;
this->take_snapshot(_(L("Add object(s)"))); std::vector<fs::path> paths;
for (const auto &file : input_files)
paths.push_back(into_path(file));
std::vector<fs::path> input_paths; wxString snapshot_label;
for (const auto &file : input_files) { assert(! paths.empty());
input_paths.push_back(into_path(file)); if (paths.size() == 1) {
} snapshot_label = _(L("Import Object"));
load_files(input_paths, true, false); snapshot_label += ": ";
snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
} else {
snapshot_label = _(L("Import Objects"));
snapshot_label += ": ";
snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
for (size_t i = 1; i < paths.size(); ++ i) {
snapshot_label += ", ";
snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str());
}
}
Plater::TakeSnapshot snapshot(this, snapshot_label);
load_files(paths, true, false);
} }
void Plater::extract_config_from_project() void Plater::extract_config_from_project()
@ -3769,17 +3814,15 @@ void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_mo
void Plater::remove_selected() void Plater::remove_selected()
{ {
this->take_snapshot(_(L("Delete Selected Objects"))); Plater::TakeSnapshot snapshot(this, _(L("Delete Selected Objects")));
this->suppress_snapshots();
this->p->view3D->delete_selected(); this->p->view3D->delete_selected();
this->allow_snapshots();
} }
void Plater::increase_instances(size_t num) void Plater::increase_instances(size_t num)
{ {
if (! can_increase_instances()) { return; } if (! can_increase_instances()) { return; }
this->take_snapshot(_(L("Increase Instances"))); Plater::TakeSnapshot snapshot(this, _(L("Increase Instances")));
int obj_idx = p->get_selected_object_idx(); int obj_idx = p->get_selected_object_idx();
@ -3815,7 +3858,7 @@ void Plater::decrease_instances(size_t num)
{ {
if (! can_decrease_instances()) { return; } if (! can_decrease_instances()) { return; }
this->take_snapshot(_(L("Decrease Instances"))); Plater::TakeSnapshot snapshot(this, _(L("Decrease Instances")));
int obj_idx = p->get_selected_object_idx(); int obj_idx = p->get_selected_object_idx();
@ -3851,16 +3894,13 @@ void Plater::set_number_of_copies(/*size_t num*/)
if (num < 0) if (num < 0)
return; return;
this->take_snapshot(wxString::Format(_(L("Set numbers of copies to %d")), num)); Plater::TakeSnapshot snapshot(this, wxString::Format(_(L("Set numbers of copies to %d")), num));
this->suppress_snapshots();
int diff = (int)num - (int)model_object->instances.size(); int diff = (int)num - (int)model_object->instances.size();
if (diff > 0) if (diff > 0)
increase_instances(diff); increase_instances(diff);
else if (diff < 0) else if (diff < 0)
decrease_instances(-diff); decrease_instances(-diff);
this->allow_snapshots();
} }
bool Plater::is_selection_empty() const bool Plater::is_selection_empty() const
@ -3884,7 +3924,7 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe
return; return;
} }
this->take_snapshot(_(L("Cut"))); Plater::TakeSnapshot snapshot(this, _(L("Cut")));
wxBusyCursor wait; wxBusyCursor wait;
const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower); const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower);

View file

@ -186,8 +186,6 @@ public:
void take_snapshot(const std::string &snapshot_name); void take_snapshot(const std::string &snapshot_name);
void take_snapshot(const wxString &snapshot_name); void take_snapshot(const wxString &snapshot_name);
void suppress_snapshots();
void allow_snapshots();
void undo(); void undo();
void redo(); void redo();
void undo_to(int selection); void undo_to(int selection);
@ -235,10 +233,46 @@ public:
const Camera& get_camera() const; const Camera& get_camera() const;
// ROII wrapper for suppressing the Undo / Redo snapshot to be taken.
class SuppressSnapshots
{
public:
SuppressSnapshots(Plater *plater) : m_plater(plater)
{
m_plater->suppress_snapshots();
}
~SuppressSnapshots()
{
m_plater->allow_snapshots();
}
private:
Plater *m_plater;
};
// ROII wrapper for taking an Undo / Redo snapshot while disabling the snapshot taking by the methods called from inside this snapshot.
class TakeSnapshot
{
public:
TakeSnapshot(Plater *plater, const wxString &snapshot_name) : m_plater(plater)
{
m_plater->take_snapshot(snapshot_name);
m_plater->suppress_snapshots();
}
~TakeSnapshot()
{
m_plater->allow_snapshots();
}
private:
Plater *m_plater;
};
private: private:
struct priv; struct priv;
std::unique_ptr<priv> p; std::unique_ptr<priv> p;
void suppress_snapshots();
void allow_snapshots();
friend class SuppressBackgroundProcessingUpdate; friend class SuppressBackgroundProcessingUpdate;
}; };

View file

@ -18,6 +18,7 @@
#include <cereal/archives/adapters.hpp> #include <cereal/archives/adapters.hpp>
#include <libslic3r/ObjectID.hpp> #include <libslic3r/ObjectID.hpp>
#include <libslic3r/Utils.hpp>
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
@ -28,11 +29,11 @@
namespace Slic3r { namespace Slic3r {
namespace UndoRedo { namespace UndoRedo {
static std::string topmost_snapsnot_name = "@@@ Topmost @@@"; static std::string topmost_snapshot_name = "@@@ Topmost @@@";
bool Snapshot::is_topmost() const bool Snapshot::is_topmost() const
{ {
return this->name == topmost_snapsnot_name; return this->name == topmost_snapshot_name;
} }
// Time interval, start is closed, end is open. // Time interval, start is closed, end is open.
@ -53,9 +54,12 @@ public:
bool operator<(const Interval &rhs) const { return (m_begin < rhs.m_begin) || (m_begin == rhs.m_begin && m_end < rhs.m_end); } bool operator<(const Interval &rhs) const { return (m_begin < rhs.m_begin) || (m_begin == rhs.m_begin && m_end < rhs.m_end); }
bool operator==(const Interval &rhs) const { return m_begin == rhs.m_begin && m_end == rhs.m_end; } bool operator==(const Interval &rhs) const { return m_begin == rhs.m_begin && m_end == rhs.m_end; }
void trim_begin(size_t new_begin) { m_begin = std::max(m_begin, new_begin); }
void trim_end(size_t new_end) { m_end = std::min(m_end, new_end); } void trim_end(size_t new_end) { m_end = std::min(m_end, new_end); }
void extend_end(size_t new_end) { assert(new_end >= m_end); m_end = new_end; } void extend_end(size_t new_end) { assert(new_end >= m_end); m_end = new_end; }
size_t memsize() const { return sizeof(this); }
private: private:
size_t m_begin; size_t m_begin;
size_t m_end; size_t m_end;
@ -75,8 +79,15 @@ public:
// If the history is empty, the ObjectHistory object could be released. // If the history is empty, the ObjectHistory object could be released.
virtual bool empty() = 0; virtual bool empty() = 0;
// Release all data before the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released.
// Return the amount of memory released.
virtual size_t release_before_timestamp(size_t timestamp) = 0;
// Release all data after the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released. // Release all data after the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released.
virtual void release_after_timestamp(size_t timestamp) = 0; // Return the amount of memory released.
virtual size_t release_after_timestamp(size_t timestamp) = 0;
// Estimated size in memory, to be used to drop least recently used snapshots.
virtual size_t memsize() const = 0;
#ifdef SLIC3R_UNDOREDO_DEBUG #ifdef SLIC3R_UNDOREDO_DEBUG
// Human readable debug information. // Human readable debug information.
@ -96,21 +107,54 @@ public:
// If the history is empty, the ObjectHistory object could be released. // If the history is empty, the ObjectHistory object could be released.
bool empty() override { return m_history.empty(); } bool empty() override { return m_history.empty(); }
// Release all data after the given timestamp. The shared pointer is NOT released. // Release all data before the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released.
void release_after_timestamp(size_t timestamp) override { size_t release_before_timestamp(size_t timestamp) override {
assert(! m_history.empty()); size_t mem_released = 0;
assert(this->valid()); if (! m_history.empty()) {
// it points to an interval which either starts with timestamp, or follows the timestamp. assert(this->valid());
auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp)); // it points to an interval which either starts with timestamp, or follows the timestamp.
if (it != m_history.begin()) { auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp));
auto it_prev = it; // Find the first iterator with begin() < timestamp.
-- it_prev; if (it == m_history.end())
assert(it_prev->begin() < timestamp); -- it;
// Trim the last interval with timestamp. while (it != m_history.begin() && it->begin() >= timestamp)
it_prev->trim_end(timestamp); -- it;
if (it->begin() < timestamp && it->end() > timestamp) {
it->trim_begin(timestamp);
if (it != m_history.begin())
-- it;
}
if (it->end() <= timestamp) {
auto it_end = ++ it;
for (it = m_history.begin(); it != it_end; ++ it)
mem_released += it->memsize();
m_history.erase(m_history.begin(), it_end);
}
assert(this->valid());
} }
m_history.erase(it, m_history.end()); return mem_released;
assert(this->valid()); }
// Release all data after the given timestamp. The shared pointer is NOT released.
size_t release_after_timestamp(size_t timestamp) override {
size_t mem_released = 0;
if (! m_history.empty()) {
assert(this->valid());
// it points to an interval which either starts with timestamp, or follows the timestamp.
auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp));
if (it != m_history.begin()) {
auto it_prev = it;
-- it_prev;
assert(it_prev->begin() < timestamp);
// Trim the last interval with timestamp.
it_prev->trim_end(timestamp);
}
for (auto it2 = it; it2 != m_history.end(); ++ it2)
mem_released += it2->memsize();
m_history.erase(it, m_history.end());
assert(this->valid());
}
return mem_released;
} }
protected: protected:
@ -138,6 +182,18 @@ public:
bool is_immutable() const override { return true; } bool is_immutable() const override { return true; }
const void* immutable_object_ptr() const { return (const void*)m_shared_object.get(); } const void* immutable_object_ptr() const { return (const void*)m_shared_object.get(); }
// Estimated size in memory, to be used to drop least recently used snapshots.
size_t memsize() const override {
size_t memsize = sizeof(*this);
if (this->is_serialized())
memsize += m_serialized.size();
else if (m_shared_object.use_count() == 1)
// Only count the shared object's memsize into the total Undo / Redo stack memsize if it is referenced from the Undo / Redo stack only.
memsize += m_shared_object->memsize();
memsize += m_history.size() * sizeof(Interval);
return memsize;
}
void save(size_t active_snapshot_time, size_t current_time) { void save(size_t active_snapshot_time, size_t current_time) {
assert(m_history.empty() || m_history.back().end() <= active_snapshot_time || assert(m_history.empty() || m_history.back().end() <= active_snapshot_time ||
// The snapshot of an immutable object may have already been taken from another mutable object. // The snapshot of an immutable object may have already been taken from another mutable object.
@ -230,7 +286,8 @@ public:
const Interval& interval() const { return m_interval; } const Interval& interval() const { return m_interval; }
size_t begin() const { return m_interval.begin(); } size_t begin() const { return m_interval.begin(); }
size_t end() const { return m_interval.end(); } size_t end() const { return m_interval.end(); }
void trim_end(size_t timestamp) { m_interval.trim_end(timestamp); } void trim_begin(size_t timestamp) { m_interval.trim_begin(timestamp); }
void trim_end (size_t timestamp) { m_interval.trim_end(timestamp); }
void extend_end(size_t timestamp) { m_interval.extend_end(timestamp); } void extend_end(size_t timestamp) { m_interval.extend_end(timestamp); }
bool operator<(const MutableHistoryInterval& rhs) const { return m_interval < rhs.m_interval; } bool operator<(const MutableHistoryInterval& rhs) const { return m_interval < rhs.m_interval; }
@ -240,6 +297,13 @@ public:
size_t size() const { return m_data->size; } size_t size() const { return m_data->size; }
size_t refcnt() const { return m_data->refcnt; } size_t refcnt() const { return m_data->refcnt; }
bool matches(const std::string& data) { return m_data->matches(data); } bool matches(const std::string& data) { return m_data->matches(data); }
size_t memsize() const {
return m_data->refcnt == 1 ?
// Count just the size of the snapshot data.
m_data->size :
// Count the size of the snapshot data divided by the number of references, rounded up.
(m_data->size + m_data->refcnt - 1) / m_data->refcnt;
}
private: private:
MutableHistoryInterval(const MutableHistoryInterval &rhs); MutableHistoryInterval(const MutableHistoryInterval &rhs);
@ -270,6 +334,15 @@ public:
bool is_mutable() const override { return true; } bool is_mutable() const override { return true; }
bool is_immutable() const override { return false; } bool is_immutable() const override { return false; }
// Estimated size in memory, to be used to drop least recently used snapshots.
size_t memsize() const override {
size_t memsize = sizeof(*this);
memsize += m_history.size() * sizeof(MutableHistoryInterval);
for (const MutableHistoryInterval &interval : m_history)
memsize += interval.memsize();
return memsize;
}
void save(size_t active_snapshot_time, size_t current_time, const std::string &data) { void save(size_t active_snapshot_time, size_t current_time, const std::string &data) {
assert(m_history.empty() || m_history.back().end() <= active_snapshot_time); assert(m_history.empty() || m_history.back().end() <= active_snapshot_time);
if (m_history.empty() || m_history.back().end() < active_snapshot_time) { if (m_history.empty() || m_history.back().end() < active_snapshot_time) {
@ -356,7 +429,17 @@ class StackImpl
{ {
public: public:
// Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning. // Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning.
StackImpl() : m_active_snapshot_time(0), m_current_time(0) {} // Initially enable Undo / Redo stack to occupy maximum 10% of the total system physical memory.
StackImpl() : m_memory_limit(std::min(Slic3r::total_physical_memory() / 10, size_t(1 * 16384 * 65536))), m_active_snapshot_time(0), m_current_time(0) {}
void set_memory_limit(size_t memsize) { m_memory_limit = memsize; }
size_t memsize() const {
size_t memsize = 0;
for (const auto &object : m_objects)
memsize += object.second->memsize();
return memsize;
}
// The Undo / Redo stack is being initialized with an empty model and an empty selection. // The Undo / Redo stack is being initialized with an empty model and an empty selection.
// The first snapshot cannot be removed. // The first snapshot cannot be removed.
@ -370,13 +453,15 @@ public:
bool has_redo_snapshot() const; bool has_redo_snapshot() const;
bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t jump_to_time); bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t jump_to_time);
bool redo(Slic3r::Model &model, size_t jump_to_time); bool redo(Slic3r::Model &model, size_t jump_to_time);
void release_least_recently_used();
// Snapshot history (names with timestamps). // Snapshot history (names with timestamps).
const std::vector<Snapshot>& snapshots() const { return m_snapshots; } const std::vector<Snapshot>& snapshots() const { return m_snapshots; }
// Timestamp of the active snapshot. // Timestamp of the active snapshot.
size_t active_snapshot_time() const { return m_active_snapshot_time; } size_t active_snapshot_time() const { return m_active_snapshot_time; }
bool temp_snapshot_active() const { return m_snapshots.back().timestamp == m_active_snapshot_time && ! m_snapshots.back().is_topmost_captured(); }
const Selection& selection_deserialized() const { return m_selection; } const Selection& selection_deserialized() const { return m_selection; }
//protected: //protected:
template<typename T, typename T_AS> ObjectID save_mutable_object(const T &object); template<typename T, typename T_AS> ObjectID save_mutable_object(const T &object);
@ -400,6 +485,7 @@ public:
if (m_active_snapshot_time > m_snapshots.back().timestamp) if (m_active_snapshot_time > m_snapshots.back().timestamp)
out += ">>>\n"; out += ">>>\n";
out += "Current time: " + std::to_string(m_current_time) + "\n"; out += "Current time: " + std::to_string(m_current_time) + "\n";
out += "Total memory occupied: " + std::to_string(this->memsize()) + "\n";
return out; return out;
} }
void print() const { void print() const {
@ -412,9 +498,10 @@ public:
#ifndef NDEBUG #ifndef NDEBUG
bool valid() const { bool valid() const {
assert(! m_snapshots.empty()); assert(! m_snapshots.empty());
assert(m_snapshots.back().is_topmost());
auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time));
assert(it != m_snapshots.begin() && it != m_snapshots.end() && it->timestamp == m_active_snapshot_time); assert(it != m_snapshots.begin() && it != m_snapshots.end() && it->timestamp == m_active_snapshot_time);
assert(m_active_snapshot_time < m_snapshots.back().timestamp || m_snapshots.back().is_topmost()); assert(m_active_snapshot_time <= m_snapshots.back().timestamp);
for (auto it = m_objects.begin(); it != m_objects.end(); ++ it) for (auto it = m_objects.begin(); it != m_objects.end(); ++ it)
assert(it->second->valid()); assert(it->second->valid());
return true; return true;
@ -436,6 +523,9 @@ private:
} }
void collect_garbage(); void collect_garbage();
// Maximum memory allowed to be occupied by the Undo / Redo stack. If the limit is exceeded,
// least recently used snapshots will be released.
size_t m_memory_limit;
// Each individual object (Model, ModelObject, ModelInstance, ModelVolume, Selection, TriangleMesh) // Each individual object (Model, ModelObject, ModelInstance, ModelVolume, Selection, TriangleMesh)
// is stored with its own history, referenced by the ObjectID. Immutable objects do not provide // is stored with its own history, referenced by the ObjectID. Immutable objects do not provide
// their own IDs, therefore there are temporary IDs generated for them and stored to m_shared_ptr_to_object_id. // their own IDs, therefore there are temporary IDs generated for them and stored to m_shared_ptr_to_object_id.
@ -655,7 +745,7 @@ void StackImpl::take_snapshot(const std::string &snapshot_name, const Slic3r::Mo
m_active_snapshot_time = m_current_time; m_active_snapshot_time = m_current_time;
// Save snapshot info of the last "current" aka "top most" state, that is only being serialized // Save snapshot info of the last "current" aka "top most" state, that is only being serialized
// if undoing an action. Such a snapshot has an invalid Model ID assigned if it was not taken yet. // if undoing an action. Such a snapshot has an invalid Model ID assigned if it was not taken yet.
m_snapshots.emplace_back(topmost_snapsnot_name, m_active_snapshot_time, 0); m_snapshots.emplace_back(topmost_snapshot_name, m_active_snapshot_time, 0);
// Release empty objects from the history. // Release empty objects from the history.
this->collect_garbage(); this->collect_garbage();
assert(this->valid()); assert(this->valid());
@ -710,9 +800,10 @@ bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selecti
} }
assert(time_to_load < m_active_snapshot_time); assert(time_to_load < m_active_snapshot_time);
assert(std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load))); assert(std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load)));
bool new_snapshot_taken = false;
if (m_active_snapshot_time == m_snapshots.back().timestamp && ! m_snapshots.back().is_topmost_captured()) { if (m_active_snapshot_time == m_snapshots.back().timestamp && ! m_snapshots.back().is_topmost_captured()) {
// The current state is temporary. The current state needs to be captured to be redoable. // The current state is temporary. The current state needs to be captured to be redoable.
this->take_snapshot(topmost_snapsnot_name, model, selection); this->take_snapshot(topmost_snapshot_name, model, selection);
// The line above entered another topmost_snapshot_name. // The line above entered another topmost_snapshot_name.
assert(m_snapshots.back().is_topmost()); assert(m_snapshots.back().is_topmost());
assert(! m_snapshots.back().is_topmost_captured()); assert(! m_snapshots.back().is_topmost_captured());
@ -722,8 +813,15 @@ bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selecti
//-- m_current_time; //-- m_current_time;
assert(m_snapshots.back().is_topmost()); assert(m_snapshots.back().is_topmost());
assert(m_snapshots.back().is_topmost_captured()); assert(m_snapshots.back().is_topmost_captured());
new_snapshot_taken = true;
} }
this->load_snapshot(time_to_load, model); this->load_snapshot(time_to_load, model);
if (new_snapshot_taken) {
// Release old snapshots if the memory allocated due to capturing the top most state is excessive.
// Don't release the snapshots here, release them first after the scene and background processing gets updated, as this will release some references
// to the shared TriangleMeshes.
//this->release_least_recently_used();
}
#ifdef SLIC3R_UNDOREDO_DEBUG #ifdef SLIC3R_UNDOREDO_DEBUG
std::cout << "After undo" << std::endl; std::cout << "After undo" << std::endl;
this->print(); this->print();
@ -764,10 +862,70 @@ void StackImpl::collect_garbage()
} }
} }
void StackImpl::release_least_recently_used()
{
assert(this->valid());
size_t current_memsize = this->memsize();
#ifdef SLIC3R_UNDOREDO_DEBUG
bool released = false;
#endif
while (current_memsize > m_memory_limit && m_snapshots.size() >= 3) {
// From which side to remove a snapshot?
assert(m_snapshots.front().timestamp < m_active_snapshot_time);
size_t mem_released = 0;
if (m_snapshots[1].timestamp == m_active_snapshot_time) {
// Remove the last snapshot.
for (auto it = m_objects.begin(); it != m_objects.end();) {
mem_released += it->second->release_after_timestamp(m_snapshots.back().timestamp);
if (it->second->empty()) {
if (it->second->immutable_object_ptr() != nullptr)
// Release the immutable object from the ptr to ObjectID map.
m_shared_ptr_to_object_id.erase(it->second->immutable_object_ptr());
mem_released += it->second->memsize();
it = m_objects.erase(it);
} else
++ it;
}
m_snapshots.pop_back();
m_snapshots.back().name = topmost_snapshot_name;
} else {
// Remove the first snapshot.
for (auto it = m_objects.begin(); it != m_objects.end();) {
mem_released += it->second->release_before_timestamp(m_snapshots[1].timestamp);
if (it->second->empty()) {
if (it->second->immutable_object_ptr() != nullptr)
// Release the immutable object from the ptr to ObjectID map.
m_shared_ptr_to_object_id.erase(it->second->immutable_object_ptr());
mem_released += it->second->memsize();
it = m_objects.erase(it);
} else
++ it;
}
m_snapshots.erase(m_snapshots.begin());
}
assert(current_memsize >= mem_released);
if (current_memsize >= mem_released)
current_memsize -= mem_released;
else
current_memsize = 0;
#ifdef SLIC3R_UNDOREDO_DEBUG
released = true;
#endif
}
assert(this->valid());
#ifdef SLIC3R_UNDOREDO_DEBUG
std::cout << "After release_least_recently_used" << std::endl;
this->print();
#endif /* SLIC3R_UNDOREDO_DEBUG */
}
// Wrappers of the private implementation. // Wrappers of the private implementation.
Stack::Stack() : pimpl(new StackImpl()) {} Stack::Stack() : pimpl(new StackImpl()) {}
Stack::~Stack() {} Stack::~Stack() {}
void Stack::take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { pimpl->take_snapshot(snapshot_name, model, selection); } void Stack::set_memory_limit(size_t memsize) { pimpl->set_memory_limit(memsize); }
size_t Stack::memsize() const { return pimpl->memsize(); }
void Stack::release_least_recently_used() { pimpl->release_least_recently_used(); }
void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection) { pimpl->take_snapshot(snapshot_name, model, selection); }
bool Stack::has_undo_snapshot() const { return pimpl->has_undo_snapshot(); } bool Stack::has_undo_snapshot() const { return pimpl->has_undo_snapshot(); }
bool Stack::has_redo_snapshot() const { return pimpl->has_redo_snapshot(); } bool Stack::has_redo_snapshot() const { return pimpl->has_redo_snapshot(); }
bool Stack::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t time_to_load) { return pimpl->undo(model, selection, time_to_load); } bool Stack::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t time_to_load) { return pimpl->undo(model, selection, time_to_load); }
@ -776,6 +934,7 @@ const Selection& Stack::selection_deserialized() const { return pimpl->selection
const std::vector<Snapshot>& Stack::snapshots() const { return pimpl->snapshots(); } const std::vector<Snapshot>& Stack::snapshots() const { return pimpl->snapshots(); }
size_t Stack::active_snapshot_time() const { return pimpl->active_snapshot_time(); } size_t Stack::active_snapshot_time() const { return pimpl->active_snapshot_time(); }
bool Stack::temp_snapshot_active() const { return pimpl->temp_snapshot_active(); }
} // namespace UndoRedo } // namespace UndoRedo
} // namespace Slic3r } // namespace Slic3r

View file

@ -55,6 +55,15 @@ public:
Stack(); Stack();
~Stack(); ~Stack();
// Set maximum memory threshold. If the threshold is exceeded, least recently used snapshots are released.
void set_memory_limit(size_t memsize);
// Estimate size of the RAM consumed by the Undo / Redo stack.
size_t memsize() const;
// Release least recently used snapshots up to the memory limit set above.
void release_least_recently_used();
// Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time.
void take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection); void take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection);
@ -78,6 +87,9 @@ public:
// The snapshot time indicates start of an operation, which is finished at the time of the following snapshot, therefore // The snapshot time indicates start of an operation, which is finished at the time of the following snapshot, therefore
// the active snapshot is the successive snapshot. The same logic applies to the time_to_load parameter of undo() and redo() operations. // the active snapshot is the successive snapshot. The same logic applies to the time_to_load parameter of undo() and redo() operations.
size_t active_snapshot_time() const; size_t active_snapshot_time() const;
// Temporary snapshot is active if the topmost snapshot is active and it has not been captured yet.
// In that case the Undo action will capture the last snapshot.
bool temp_snapshot_active() const;
// After load_snapshot() / undo() / redo() the selection is deserialized into a list of ObjectIDs, which needs to be converted // After load_snapshot() / undo() / redo() the selection is deserialized into a list of ObjectIDs, which needs to be converted
// into the list of GLVolume pointers once the 3D scene is updated. // into the list of GLVolume pointers once the 3D scene is updated.