Compare commits

..
Author SHA1 Message Date
ExPikaPaka d7b86a106d Stop cloning config defaults twice and building selectors for unpainted meshes
create_default_option() cloned the default value only to ask whether it is
nullable, then cloned again in both branches and returned, leaving the delete
unreachable. That is one leaked ConfigOption per enum definition per config
load, and it was the only unreachable allocation a leak checker found anywhere
in the slicing path.

get_extruders() built a full TriangleSelector, one node per facet, to ask which
extruders a volume paints, even when it carries no painting at all. Measured on
an unpainted 597k facet volume: 38.8 MB retained and 40 to 52 ms per call, both
down to nothing, with the same answer. get_facets() takes the same short circuit
for any state other than NONE, where an empty annotation can only return an empty
set anyway.
2026-10-01 09:15:49 +02:00
5 changed files with 24 additions and 82 deletions
+1 -3
View File
@@ -304,8 +304,7 @@ ConfigOption* ConfigOptionDef::create_default_option() const
return new ConfigOptionEnumGeneric(this->enum_keys_map, this->default_value->getInt());
if (type == coEnums) {
auto dft = this->default_value->clone();
if (dft->nullable()) {
if (this->default_value->nullable()) {
ConfigOptionEnumsGenericNullable *opt = dynamic_cast<ConfigOptionEnumsGenericNullable *>(this->default_value->clone());
opt->keys_map = this->enum_keys_map;
return opt;
@@ -314,7 +313,6 @@ ConfigOption* ConfigOptionDef::create_default_option() const
opt->keys_map = this->enum_keys_map;
return opt;
}
delete dft;
}
return this->default_value->clone();
+2 -6
View File
@@ -2582,12 +2582,8 @@ void GCodeProcessorResult::reset() {
//BBS: add mutex for protection of gcode result
lock();
// 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<MoveVertex>();
lines_ends = std::vector<size_t>();
moves.clear();
lines_ends.clear();
printable_area = Pointfs();
//BBS: add bed exclude area
bed_exclude_area = Pointfs();
-50
View File
@@ -369,56 +369,6 @@ 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;
}
+18 -17
View File
@@ -2087,11 +2087,6 @@ void ModelVolume::reset_extra_facets()
this->seam_facets.reset();
this->mmu_segmentation_facets.reset();
this->fuzzy_skin_facets.reset();
// Texture-displacement paint data has no remap-across-topology-change support yet (see
// build_texture_displacement()'s documented limitation), so it must be dropped here rather
// than left referring to a mesh that no longer matches it.
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
this->texture_displacement_facet(i).reset();
}
std::optional<TriangleSelector::SavedPainting> ModelVolume::save_painting() const
@@ -2653,16 +2648,22 @@ std::vector<int> ModelVolume::get_extruders() const
return std::vector<int>();
if (mmu_segmentation_facets.timestamp() != mmuseg_ts) {
std::vector<indexed_triangle_set> its_per_type;
mmuseg_extruders.clear();
mmuseg_ts = mmu_segmentation_facets.timestamp();
mmu_segmentation_facets.get_facets(*this, its_per_type);
for (int idx = 1; idx < its_per_type.size(); idx++) {
indexed_triangle_set& its = its_per_type[idx];
if (its.indices.empty())
continue;
// ORCA: without painting data every facet keeps its default (NONE) state, so no extruder
// other than the volume's own one can be painted on it. Skip get_facets() then: it builds a
// TriangleSelector with one node per facet over the whole mesh (tens of MiB on a dense one)
// only to hand back empty sets for every extruder.
if (! mmu_segmentation_facets.empty()) {
std::vector<indexed_triangle_set> its_per_type;
mmu_segmentation_facets.get_facets(*this, its_per_type);
for (int idx = 1; idx < its_per_type.size(); idx++) {
indexed_triangle_set& its = its_per_type[idx];
if (its.indices.empty())
continue;
mmuseg_extruders.push_back(idx);
mmuseg_extruders.push_back(idx);
}
}
}
@@ -2994,11 +2995,6 @@ void ModelVolume::assign_new_unique_ids_recursive()
seam_facets.set_new_unique_id();
mmu_segmentation_facets.set_new_unique_id();
fuzzy_skin_facets.set_new_unique_id();
// As set_new_unique_id() already does: the undo/redo stack stores FacetsAnnotation contents keyed
// by ObjectID, so a clone left sharing these ids with its source can be handed the source's mask
// on an undo - after which a paint mask and the mesh it was recorded against no longer match.
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
texture_displacement_facet(i).set_new_unique_id();
}
void ModelVolume::rotate(double angle, Axis axis)
@@ -3636,6 +3632,11 @@ ModelInstanceEPrintVolumeState ModelInstance::calc_print_volume_state(const Buil
indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, EnforcerBlockerType type) const
{
// ORCA: nothing is painted, so only the NONE state can have facets. Answering for any other
// state needs no TriangleSelector (one node per facet of the whole mesh) at all.
if (this->empty() && type != EnforcerBlockerType::NONE)
return {};
TriangleSelector selector(mv.mesh());
// Reset of TriangleSelector is done inside TriangleSelector's constructor, so we don't need it to perform it again in deserialize().
selector.deserialize(m_data, false);
+3 -6
View File
@@ -883,17 +883,15 @@ 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<PathVertex>().swap(m_vertices);
std::vector<float>().swap(m_vertices_colors);
for (std::vector<float>& times : m_layer_start_times)
std::vector<float>().swap(times);
std::vector<uint32_t>().swap(m_layer_first_vertex);
std::vector<float>().swap(m_colors_scratch);
// 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<>();
m_valid_lines_bitset.clear();
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
m_cog_marker.reset();
#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS
@@ -1814,7 +1812,6 @@ 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<float>& times : m_layer_start_times)
ret += STDVEC_MEMSIZE(times, float);
ret += STDVEC_MEMSIZE(m_layer_first_vertex, uint32_t);