Compare commits

..
Author SHA1 Message Date
ExPikaPaka bea412fddf 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.
2026-10-01 09:15:15 +02:00
14 changed files with 162 additions and 235 deletions
+6 -2
View File
@@ -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<MoveVertex>();
lines_ends = std::vector<size_t>();
printable_area = Pointfs();
//BBS: add bed exclude area
bed_exclude_area = Pointfs();
+50
View File
@@ -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;
}
+6 -3
View File
@@ -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<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);
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<float>& times : m_layer_start_times)
ret += STDVEC_MEMSIZE(times, float);
ret += STDVEC_MEMSIZE(m_layer_first_vertex, uint32_t);
+8 -23
View File
@@ -819,9 +819,7 @@ int OrcaCloudServiceAgent::user_logout(bool request)
}
}
// An explicit logout also wipes the backend the token storage option is not using, so a token
// stranded by switching that option cannot sign the account back in later.
clear_session(/*all_backends=*/request);
clear_session();
return BAMBU_NETWORK_SUCCESS;
}
@@ -1606,9 +1604,7 @@ void OrcaCloudServiceAgent::persist_user_secret(const std::string& secret)
}
}
if (stored) {
secret_stored = true;
}
(void) stored;
}
bool OrcaCloudServiceAgent::load_user_secret(std::string& out_secret)
@@ -1648,7 +1644,6 @@ bool OrcaCloudServiceAgent::load_user_secret(std::string& out_secret)
}
if (integrity_ok && aes256gcm_decrypt(encoded_payload, key, plain) && !plain.empty()) {
secret_stored = true;
out_secret = plain;
// Upgrade legacy payloads to signed format
if (payload.rfind("v2:", 0) != 0) {
@@ -1666,7 +1661,6 @@ bool OrcaCloudServiceAgent::load_user_secret(std::string& out_secret)
if (store.Load(SECRET_STORE_SERVICE, username, secret) && secret.IsOk()) {
out_secret.assign(static_cast<const char*>(secret.GetData()), secret.GetSize());
if (!out_secret.empty()) {
secret_stored = true;
return true;
}
}
@@ -1676,20 +1670,11 @@ bool OrcaCloudServiceAgent::load_user_secret(std::string& out_secret)
return false;
}
void OrcaCloudServiceAgent::clear_user_secret(bool all_backends)
void OrcaCloudServiceAgent::clear_user_secret()
{
// Nothing this process loaded or saved: leave the store alone. Deleting would only cost a
// keychain round trip (or a hang while the keychain is unresponsive) and could remove a
// login another instance just saved.
if (!secret_stored.exchange(false) && !all_backends) {
return;
}
if (all_backends || !m_use_encrypted_token_file) {
wxSecretStore store = wxSecretStore::GetDefault();
if (store.IsOk()) {
store.Delete(SECRET_STORE_SERVICE);
}
wxSecretStore store = wxSecretStore::GetDefault();
if (store.IsOk()) {
store.Delete(SECRET_STORE_SERVICE);
}
compute_fallback_path();
@@ -2038,13 +2023,13 @@ bool OrcaCloudServiceAgent::set_user_session(const json& session_json, bool noti
return success;
}
void OrcaCloudServiceAgent::clear_session(bool all_backends)
void OrcaCloudServiceAgent::clear_session()
{
{
std::lock_guard<std::mutex> lock(session_mutex);
session = SessionInfo{};
}
clear_user_secret(all_backends);
clear_user_secret();
}
// ============================================================================
+2 -7
View File
@@ -326,7 +326,7 @@ public:
void persist_user_secret(const std::string& secret);
bool load_user_secret(std::string& out_secret);
void clear_user_secret(bool all_backends = false);
void clear_user_secret();
// Token refresh helpers
bool refresh_if_expiring(std::chrono::seconds skew, const std::string& reason);
@@ -344,7 +344,7 @@ public:
bool persist = true);
// Accepts either nested Orca cloud / GoTrue session JSON or flat WebView token JSON.
bool set_user_session(const nlohmann::json& session_json, bool notify_login = true);
void clear_session(bool all_backends = false);
void clear_session();
static std::string generate_uuid_for_setting_id(const std::string& name, const std::string& user_id = "");
@@ -413,11 +413,6 @@ private:
// Member variables - auth state
PkceBundle pkce_bundle;
std::string secret_fallback_path;
// Set once this process has read a secret from the store or written one. Unless the user logs
// out explicitly, clear_user_secret() only touches the store while it is set, so a logged-out
// instance (the GUI polls the login status every 2 s) makes no keychain calls and cannot wipe
// a login another instance saved.
std::atomic_bool secret_stored{false};
SessionHandler session_handler;
OnLoginCompleteHandler on_login_complete_handler;
SessionInfo session;
-1
View File
@@ -13,7 +13,6 @@ add_executable(${_TEST_NAME}_tests
test_prebuild_queue.cpp
test_staged_build.cpp
test_network_versions.cpp
test_orca_cloud_agent.cpp
test_action_source.cpp
test_plugin_host_api.cpp
# Exercise seam enums and predicates through the embedded Python host API.
+1 -9
View File
@@ -6,7 +6,6 @@
#include <boost/filesystem.hpp>
#include <memory.h>
#include <stdexcept>
#include <string>
#include <pybind11/embed.h>
#include <pybind11/pybind11.h>
@@ -26,15 +25,8 @@ void ensure_python_initialized()
config.parse_argv = 0;
const auto python_home = boost::dll::program_location().parent_path() / "python";
#ifdef _WIN32
const auto stdlib = python_home / "Lib";
#else
const auto stdlib = python_home / "lib" /
("python" + std::to_string(PY_MAJOR_VERSION) + "." + std::to_string(PY_MINOR_VERSION));
#endif
// Only a real runtime: a stray python/ folder (packages a test left behind) is not a home.
if (boost::filesystem::exists(stdlib / "encodings")) {
if (boost::filesystem::exists(python_home)) {
const std::string home = python_home.string();
const PyStatus status = PyConfig_SetBytesString(&config, &config.home, home.c_str());
@@ -1,6 +1,39 @@
#include <catch2/catch_all.hpp>
#include "slic3r/Utils/Http.hpp"
#include "slic3r/Utils/OrcaCloudServiceAgent.hpp"
namespace {
nlohmann::json flat_session_json(const nlohmann::json& fields)
{
nlohmann::json session = {
{"access_token", "test-token"},
{"user_id", "test-user-id"}
};
session.update(fields);
return session;
}
nlohmann::json nested_session_json(const nlohmann::json& metadata)
{
return {
{"access_token", "test-token"},
{"user", {
{"id", "test-user-id"},
{"user_metadata", metadata}
}}
};
}
std::string resolved_display_name(const nlohmann::json& session)
{
Slic3r::OrcaCloudServiceAgent agent("");
REQUIRE(agent.set_user_session(session, false));
return agent.get_user_nickname();
}
} // namespace
TEST_CASE("Check SSL certificates paths", "[Http][NotWorking]") {
@@ -20,6 +53,62 @@ TEST_CASE("Check SSL certificates paths", "[Http][NotWorking]") {
REQUIRE(status == 200);
}
TEST_CASE("Orca cloud flat session resolves display name consistently", "[OrcaCloudServiceAgent]")
{
CHECK(resolved_display_name(flat_session_json({
{"username", "orca_username"},
{"display_name", "Display Name"},
{"nickname", "Nickname"}
})) == "Display Name");
CHECK(resolved_display_name(flat_session_json({
{"username", "orca_username"},
{"nickname", "Nickname"}
})) == "Nickname");
CHECK(resolved_display_name(flat_session_json({
{"username", "orca_username"},
{"full_name", "Full Name"}
})) == "Full Name");
CHECK(resolved_display_name(flat_session_json({
{"username", "orca_username"},
{"name", "Provider Name"}
})) == "Provider Name");
CHECK(resolved_display_name(flat_session_json({
{"username", "orca_username"}
})) == "orca_username");
}
TEST_CASE("Orca cloud nested session resolves display name consistently", "[OrcaCloudServiceAgent]")
{
CHECK(resolved_display_name(nested_session_json({
{"username", "orca_username"},
{"display_name", "Display Name"},
{"nickname", "Nickname"}
})) == "Display Name");
CHECK(resolved_display_name(nested_session_json({
{"username", "orca_username"},
{"nickname", "Nickname"}
})) == "Nickname");
CHECK(resolved_display_name(nested_session_json({
{"username", "orca_username"},
{"full_name", "Full Name"}
})) == "Full Name");
CHECK(resolved_display_name(nested_session_json({
{"username", "orca_username"},
{"name", "Provider Name"}
})) == "Provider Name");
CHECK(resolved_display_name(nested_session_json({
{"username", "orca_username"}
})) == "orca_username");
}
TEST_CASE("Http digest authentication", "[Http][NotWorking]") {
Slic3r::Http g = Slic3r::Http::get("https://httpbingo.org/digest-auth/auth/guest/guest");
-173
View File
@@ -1,173 +0,0 @@
#include <catch2/catch_all.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <memory>
#include <string>
#include "slic3r/Utils/OrcaCloudServiceAgent.hpp"
#include "test_utils.hpp"
using namespace Slic3r;
namespace fs = boost::filesystem;
namespace {
// The encrypted token file is the one secret backend a test can observe without a system
// keychain. Every agent pointed at the same directory shares it, like separate app instances
// share the keychain entry.
std::unique_ptr<OrcaCloudServiceAgent> make_file_backed_agent(const fs::path& dir)
{
auto agent = std::make_unique<OrcaCloudServiceAgent>(dir.string());
agent->set_use_encrypted_token_file(true);
agent->set_config_dir(dir.string());
return agent;
}
fs::path secret_file(const fs::path& dir) { return dir / secret_constants::USER_SECRET_FILENAME; }
nlohmann::json flat_session_json(const nlohmann::json& fields)
{
nlohmann::json session = {
{"access_token", "test-token"},
{"user_id", "test-user-id"}
};
session.update(fields);
return session;
}
nlohmann::json nested_session_json(const nlohmann::json& metadata)
{
return {
{"access_token", "test-token"},
{"user", {
{"id", "test-user-id"},
{"user_metadata", metadata}
}}
};
}
// set_user_session() persists the session, so it goes to a throwaway token file rather than the
// system keychain of whoever runs the tests.
std::string resolved_display_name(const nlohmann::json& session)
{
ScopedTemporaryDir dir("orca-secret");
auto agent = make_file_backed_agent(dir.path());
REQUIRE(agent->set_user_session(session, false));
return agent->get_user_nickname();
}
} // namespace
TEST_CASE("Logging out removes the secret this instance saved", "[OrcaCloudServiceAgent]")
{
ScopedTemporaryDir dir("orca-secret");
auto agent = make_file_backed_agent(dir.path());
agent->persist_user_secret("refresh-token");
REQUIRE(fs::exists(secret_file(dir.path())));
agent->user_logout(false);
CHECK_FALSE(fs::exists(secret_file(dir.path())));
}
TEST_CASE("Logging out removes a secret this instance loaded from the store", "[OrcaCloudServiceAgent]")
{
ScopedTemporaryDir dir("orca-secret");
make_file_backed_agent(dir.path())->persist_user_secret("refresh-token");
auto agent = make_file_backed_agent(dir.path());
std::string secret;
REQUIRE(agent->load_user_secret(secret));
CHECK(secret == "refresh-token");
agent->user_logout(false);
CHECK_FALSE(fs::exists(secret_file(dir.path())));
}
TEST_CASE("Logging out leaves a secret this instance never loaded or saved alone", "[OrcaCloudServiceAgent]")
{
ScopedTemporaryDir dir("orca-secret");
make_file_backed_agent(dir.path())->persist_user_secret("refresh-token");
// A logged-out instance is asked to log out on every login-status poll.
auto other = make_file_backed_agent(dir.path());
other->user_logout(false);
other->user_logout(false);
CHECK(fs::exists(secret_file(dir.path())));
std::string secret;
REQUIRE(make_file_backed_agent(dir.path())->load_user_secret(secret));
CHECK(secret == "refresh-token");
}
TEST_CASE("Logging out leaves a secret this instance could not read alone", "[OrcaCloudServiceAgent]")
{
ScopedTemporaryDir dir("orca-secret");
// Written under another encryption key, e.g. by another OS user sharing the data directory.
fs::ofstream(secret_file(dir.path())) << "v2:0000:not-a-payload-this-user-can-decrypt";
auto agent = make_file_backed_agent(dir.path());
std::string secret;
REQUIRE_FALSE(agent->load_user_secret(secret));
agent->user_logout(false);
CHECK(fs::exists(secret_file(dir.path())));
}
TEST_CASE("Orca cloud flat session resolves display name consistently", "[OrcaCloudServiceAgent]")
{
CHECK(resolved_display_name(flat_session_json({
{"username", "orca_username"},
{"display_name", "Display Name"},
{"nickname", "Nickname"}
})) == "Display Name");
CHECK(resolved_display_name(flat_session_json({
{"username", "orca_username"},
{"nickname", "Nickname"}
})) == "Nickname");
CHECK(resolved_display_name(flat_session_json({
{"username", "orca_username"},
{"full_name", "Full Name"}
})) == "Full Name");
CHECK(resolved_display_name(flat_session_json({
{"username", "orca_username"},
{"name", "Provider Name"}
})) == "Provider Name");
CHECK(resolved_display_name(flat_session_json({
{"username", "orca_username"}
})) == "orca_username");
}
TEST_CASE("Orca cloud nested session resolves display name consistently", "[OrcaCloudServiceAgent]")
{
CHECK(resolved_display_name(nested_session_json({
{"username", "orca_username"},
{"display_name", "Display Name"},
{"nickname", "Nickname"}
})) == "Display Name");
CHECK(resolved_display_name(nested_session_json({
{"username", "orca_username"},
{"nickname", "Nickname"}
})) == "Nickname");
CHECK(resolved_display_name(nested_session_json({
{"username", "orca_username"},
{"full_name", "Full Name"}
})) == "Full Name");
CHECK(resolved_display_name(nested_session_json({
{"username", "orca_username"},
{"name", "Provider Name"}
})) == "Provider Name");
CHECK(resolved_display_name(nested_session_json({
{"username", "orca_username"}
})) == "orca_username");
}
@@ -38,9 +38,6 @@ namespace {
// before this destructor's shutdown() runs.
struct ScopedPluginManager
{
// Before initialize(): the interpreter creates {data_dir}/python/packages and {data_dir}/log,
// which would otherwise land in the working directory.
ScopedDataDir python_data_dir{"plugin-python"};
bool initialized = PluginManager::instance().initialize();
~ScopedPluginManager()
@@ -31,9 +31,6 @@ namespace {
// same as any other plugin.
struct ScopedManagerShutdown
{
// Before initialize(): the interpreter creates {data_dir}/python/packages and {data_dir}/log,
// which would otherwise land in the working directory.
ScopedDataDir python_data_dir{"plugin-python"};
bool initialized = PluginManager::instance().initialize();
~ScopedManagerShutdown()
@@ -42,9 +42,6 @@ namespace {
// Declare this FIRST in a test so it is destroyed last.
struct ScopedPluginManager
{
// Before initialize(): the interpreter creates {data_dir}/python/packages and {data_dir}/log,
// which would otherwise land in the working directory.
ScopedDataDir python_data_dir{"plugin-python"};
bool initialized = false;
ScopedPluginManager() { initialized = PluginManager::instance().initialize(); }
@@ -12,8 +12,6 @@
#include <memory>
#include <string>
#include "plugin_test_utils.hpp"
namespace py = pybind11;
using namespace Slic3r;
@@ -23,9 +21,6 @@ namespace {
// into Python unless PythonInterpreter::instance() reports initialized.
struct ScopedPluginManager
{
// Before initialize(): the interpreter creates {data_dir}/python/packages and {data_dir}/log,
// which would otherwise land in the working directory.
ScopedDataDir python_data_dir{"plugin-python"};
bool initialized = PluginManager::instance().initialize();
~ScopedPluginManager()
@@ -35,9 +35,6 @@ namespace {
struct ScopedPluginManager
{
// Before initialize(): the interpreter creates {data_dir}/python/packages and {data_dir}/log,
// which would otherwise land in the working directory.
ScopedDataDir python_data_dir{"plugin-python"};
bool initialized = false;
ScopedPluginManager() { initialized = PluginManager::instance().initialize(); }