From bea412fddfa894143cfa641fd1ab24e7c478a496 Mon Sep 17 00:00:00 2001 From: ExPikaPaka Date: Thu, 1 Oct 2026 09:15:15 +0200 Subject: [PATCH] Move the G-code processing result instead of copying it GCodeProcessorResult declares a copy assignment, which suppresses the implicit move assignment, so `*result = std::move(processor.extract_result())` binds to the copy and duplicates the whole moves array: 96 bytes per move, measured at 147.7 MiB in one allocation for a 597k facet model at 0.08 mm, with the source staying alive until the export returns. Add the move assignment. It assigns exactly the same 39 members in the same order as the copy, including the ones the copy deliberately leaves alone, so a target that carries its own filament maps and nozzle type keeps them. A move constructor is intentionally not added: the type holds a std::mutex, so it is neither copy nor move constructible today and a partial one would leave ten members uninitialised. reset() now releases the two print sized vectors rather than clearing them. Its callers are the paths that discard a result, so the memory went back only at the next slice. ViewerImpl::reset() does the same for the two vectors sized by the print, which is what keeps a discarded preview resident, and counts m_vertices_colors in the reported CPU memory, where it was missing. --- src/libslic3r/GCode/GCodeProcessor.cpp | 8 +++-- src/libslic3r/GCode/GCodeProcessor.hpp | 50 ++++++++++++++++++++++++++ src/libvgcode/src/ViewerImpl.cpp | 9 +++-- 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index e03be228d0..505a28335d 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -2582,8 +2582,12 @@ void GCodeProcessorResult::reset() { //BBS: add mutex for protection of gcode result lock(); - moves.clear(); - lines_ends.clear(); + // release rather than clear: these two are sized by the print - one entry per move and one + // per g-code line - and a reset is where the memory is expected to go back to the allocator + // (see BackgroundSlicingProcess::apply()). The capacity would not be reused anyway: the + // result is refilled by move-assigning the processor's own result. + moves = std::vector(); + lines_ends = std::vector(); printable_area = Pointfs(); //BBS: add bed exclude area bed_exclude_area = Pointfs(); diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 192a80a43d..a99a5188d6 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -369,6 +369,56 @@ class Print; initial_layer_time = other.initial_layer_time; #if ENABLE_GCODE_VIEWER_STATISTICS time = other.time; +#endif + return *this; + } + // Orca: the user-declared copy assignment above suppresses the implicit move assignment, so + // `*result = std::move(processor.extract_result())` used to deep copy 'moves' (one MoveVertex + // per move, gigabytes on a large print) while the source stayed alive. This moves exactly the + // same members as the copy above, with the same omissions, so the members the copy leaves + // untouched on the target are left untouched here too. + GCodeProcessorResult& operator=(GCodeProcessorResult &&other) + { + filename = std::move(other.filename); + id = other.id; + moves = std::move(other.moves); + lines_ends = std::move(other.lines_ends); + printable_area = std::move(other.printable_area); + bed_exclude_area = std::move(other.bed_exclude_area); + wrapping_exclude_area = std::move(other.wrapping_exclude_area); + toolpath_outside = other.toolpath_outside; + label_object_enabled = other.label_object_enabled; + long_retraction_when_cut = other.long_retraction_when_cut; + timelapse_warning_code = other.timelapse_warning_code; + printable_height = other.printable_height; + settings_ids = std::move(other.settings_ids); + filaments_count = other.filaments_count; + extruder_colors = std::move(other.extruder_colors); + filament_diameters = std::move(other.filament_diameters); + filament_densities = std::move(other.filament_densities); + filament_costs = std::move(other.filament_costs); + print_statistics = std::move(other.print_statistics); + custom_gcode_per_print_z = std::move(other.custom_gcode_per_print_z); + spiral_vase_mode = other.spiral_vase_mode; + warnings = std::move(other.warnings); + bed_type = other.bed_type; + gcode_check_result = std::move(other.gcode_check_result); + limit_filament_maps = std::move(other.limit_filament_maps); + filament_printable_reuslt = std::move(other.filament_printable_reuslt); + nozzle_group_result = std::move(other.nozzle_group_result); + extruder_types = std::move(other.extruder_types); + printer_extruder_variant = std::move(other.printer_extruder_variant); + printer_extruder_id = std::move(other.printer_extruder_id); + layer_filaments = std::move(other.layer_filaments); + filament_change_sequence = std::move(other.filament_change_sequence); + used_mixed_filaments = std::move(other.used_mixed_filaments); + nozzle_change_sequence = std::move(other.nozzle_change_sequence); + optimal_assignment = std::move(other.optimal_assignment); + filament_change_count_map = std::move(other.filament_change_count_map); + skippable_part_time = std::move(other.skippable_part_time); + initial_layer_time = other.initial_layer_time; +#if ENABLE_GCODE_VIEWER_STATISTICS + time = other.time; #endif return *this; } diff --git a/src/libvgcode/src/ViewerImpl.cpp b/src/libvgcode/src/ViewerImpl.cpp index 8212717931..e3e106bb59 100644 --- a/src/libvgcode/src/ViewerImpl.cpp +++ b/src/libvgcode/src/ViewerImpl.cpp @@ -883,15 +883,17 @@ void ViewerImpl::reset() m_used_extruders.clear(); m_total_time = { 0.0f, 0.0f }; m_travels_time = { 0.0f, 0.0f }; - m_vertices.clear(); - m_vertices_colors.clear(); // swap rather than clear: these are sized by the print, and a reset means the memory // should go back, not sit reserved until the next load + std::vector().swap(m_vertices); + std::vector().swap(m_vertices_colors); for (std::vector& times : m_layer_start_times) std::vector().swap(times); std::vector().swap(m_layer_first_vertex); std::vector().swap(m_colors_scratch); - m_valid_lines_bitset.clear(); + // BitSet::clear() only zeroes the bits, it keeps the blocks allocated; load() builds a new + // bitset anyway and it is never read while m_vertices is empty + m_valid_lines_bitset = BitSet<>(); #if VGCODE_ENABLE_COG_AND_TOOL_MARKERS m_cog_marker.reset(); #endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS @@ -1812,6 +1814,7 @@ size_t ViewerImpl::get_used_cpu_memory() const ret += sizeof(m_extrusion_roles_colors); ret += sizeof(m_options_colors); ret += STDVEC_MEMSIZE(m_vertices, PathVertex); + ret += STDVEC_MEMSIZE(m_vertices_colors, float); for (const std::vector& times : m_layer_start_times) ret += STDVEC_MEMSIZE(times, float); ret += STDVEC_MEMSIZE(m_layer_first_vertex, uint32_t);