From 88901a969fdefd92c30f6d50b21349995e891de8 Mon Sep 17 00:00:00 2001 From: ExPikaPaka Date: Mon, 15 Jun 2026 09:28:59 +0200 Subject: [PATCH] Add partial cache generation when only one of the vendros is changed to speed up recalculation time --- src/libslic3r/PresetBundle.cpp | 71 ++++++++++++++++++----------- src/libslic3r/PresetBundleCache.cpp | 54 +++++++++++++++------- src/libslic3r/PresetBundleCache.hpp | 9 +++- 3 files changed, 89 insertions(+), 45 deletions(-) diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index c3d77dcae3..1665fbcb17 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -2196,45 +2196,59 @@ std::pair PresetBundle::load_system_pre dir = (boost::filesystem::path(data_dir())).make_preferred(); // Try loading from binary cache first (skips JSON parsing on cache hit). + // partial_dirty_vendors is non-empty when some vendors changed but others are still cached. + std::set partial_dirty_vendors; if (!validation_mode) { const auto t0 = std::chrono::steady_clock::now(); PresetBundleCache::SystemPresetsCache cache; const std::string cache_file = PresetBundleCache::SystemPresetsCache::cache_path(); - if (cache.load(cache_file) && cache.is_valid(dir.string())) { - cache.apply(*this); - update_system_maps(); - const auto ms = std::chrono::duration_cast( - std::chrono::steady_clock::now() - t0).count(); - BOOST_LOG_TRIVIAL(info) << "PresetBundle: system presets loaded from cache in " << ms << " ms"; - return {PresetsConfigSubstitutions{}, ""}; - } - - // Try bundled cache shipped with the installer (first launch, before user cache exists). - // Validates against resources/profiles/ — the same directory it was generated from in CI. - { - const std::string bundled_dir = - (boost::filesystem::path(resources_dir()) / "profiles").make_preferred().string(); - PresetBundleCache::SystemPresetsCache bundled; - if (bundled.load(PresetBundleCache::SystemPresetsCache::bundled_cache_path()) && - bundled.is_valid(bundled_dir)) { - bundled.apply(*this); - update_system_maps(); - // Promote to user cache so subsequent launches skip this check. - bundled.save(cache_file); - const auto ms = std::chrono::duration_cast( - std::chrono::steady_clock::now() - t0).count(); - BOOST_LOG_TRIVIAL(info) << "PresetBundle: system presets loaded from bundled cache in " << ms << " ms"; - return {PresetsConfigSubstitutions{}, ""}; + if (cache.load(cache_file)) { + std::set dirty; + if (cache.get_dirty_vendors(dir.string(), dirty)) { + if (dirty.empty()) { + // Full hit — every vendor is unchanged. + cache.apply(*this); + update_system_maps(); + const auto ms = std::chrono::duration_cast( + std::chrono::steady_clock::now() - t0).count(); + BOOST_LOG_TRIVIAL(info) << "PresetBundle: system presets loaded from cache in " << ms << " ms"; + return {PresetsConfigSubstitutions{}, ""}; + } + // Partial hit — restore clean vendors from cache, re-parse only dirty ones. + BOOST_LOG_TRIVIAL(info) << "PresetBundle: partial cache hit, " << dirty.size() + << " vendor(s) changed — re-parsing those only"; + cache.apply_partial(*this, dirty); + partial_dirty_vendors = std::move(dirty); } + // else: structural mismatch (format version or option count changed) → full re-parse. } - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " cache miss, falling back to JSON load"; + if (partial_dirty_vendors.empty()) { + // No partial hit — try bundled cache shipped with the installer (first launch). + { + const std::string bundled_dir = + (boost::filesystem::path(resources_dir()) / "profiles").make_preferred().string(); + PresetBundleCache::SystemPresetsCache bundled; + if (bundled.load(PresetBundleCache::SystemPresetsCache::bundled_cache_path()) && + bundled.is_valid(bundled_dir)) { + bundled.apply(*this); + update_system_maps(); + // Promote to user cache so subsequent launches skip this check. + bundled.save(cache_file); + const auto ms = std::chrono::duration_cast( + std::chrono::steady_clock::now() - t0).count(); + BOOST_LOG_TRIVIAL(info) << "PresetBundle: system presets loaded from bundled cache in " << ms << " ms"; + return {PresetsConfigSubstitutions{}, ""}; + } + } + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " cache miss, falling back to JSON load"; + } } const auto json_load_t0 = std::chrono::steady_clock::now(); PresetsConfigSubstitutions substitutions; std::string errors_cummulative; - bool first = true; + bool first = partial_dirty_vendors.empty(); // false in partial mode: clean vendors already applied std::vector vendor_names; // store all vendor names in vendor_names for (auto& dir_entry : boost::filesystem::directory_iterator(dir)) { @@ -2256,6 +2270,9 @@ std::pair PresetBundle::load_system_pre std::vector other_vendors; other_vendors.reserve(vendor_names.size()); for (auto& vn : vendor_names) { + // In partial mode, skip vendors already loaded from cache. + if (!partial_dirty_vendors.empty() && !partial_dirty_vendors.count(vn)) + continue; if (vn == ORCA_FILAMENT_LIBRARY) orca_lib_vendor = vn; else if (!(validation_mode && !vendor_to_validate.empty() && vn != vendor_to_validate)) diff --git a/src/libslic3r/PresetBundleCache.cpp b/src/libslic3r/PresetBundleCache.cpp index 48df8acf53..9503948b6e 100644 --- a/src/libslic3r/PresetBundleCache.cpp +++ b/src/libslic3r/PresetBundleCache.cpp @@ -124,9 +124,14 @@ std::string SystemPresetsCache::bundled_cache_path() bool SystemPresetsCache::is_valid(const std::string& system_dir) const { - if (format_version != FORMAT_VERSION) - return false; - if (config_options_count != print_config_def.options.size()) + std::set dummy; + return get_dirty_vendors(system_dir, dummy) && dummy.empty(); +} + +bool SystemPresetsCache::get_dirty_vendors(const std::string& system_dir, std::set& out_dirty) const +{ + out_dirty.clear(); + if (format_version != FORMAT_VERSION || config_options_count != print_config_def.options.size()) return false; std::map current; try { @@ -143,12 +148,15 @@ bool SystemPresetsCache::is_valid(const std::string& system_dir) const BOOST_LOG_TRIVIAL(warning) << "PresetBundleCache: directory scan failed: " << e.what(); return false; } - if (current.size() != vendor_versions.size()) - return false; + // A vendor was removed from disk — safest to force a full re-parse. + for (const auto& [name, ver] : vendor_versions) + if (current.find(name) == current.end()) + return false; + // Collect vendors with changed or new version strings. for (const auto& [name, ver] : current) { auto it = vendor_versions.find(name); if (it == vendor_versions.end() || it->second != ver) - return false; + out_dirty.insert(name); } return true; } @@ -234,9 +242,16 @@ void SystemPresetsCache::capture(const PresetBundle& bundle, const std::string& } void SystemPresetsCache::apply(PresetBundle& bundle) const +{ + apply_partial(bundle, {}); +} + +void SystemPresetsCache::apply_partial(PresetBundle& bundle, const std::set& skip_vendor_ids) const { bundle.reset(false); for (const auto& cvp : vendor_profiles) { + if (skip_vendor_ids.count(cvp.id)) + continue; VendorProfile vp(cvp.id); vp.name = cvp.name; vp.config_update_url = cvp.config_update_url; @@ -273,10 +288,12 @@ void SystemPresetsCache::apply(PresetBundle& bundle) const bundle.vendors.emplace(cvp.id, std::move(vp)); } - auto apply_col = [&bundle](const std::vector& cached, - PresetCollection& coll, - bool is_filaments) { + auto apply_col = [&](const std::vector& cached, + PresetCollection& coll, + bool is_filaments) { for (const auto& cp : cached) { + if (skip_vendor_ids.count(cp.vendor_id)) + continue; Semver version; if (!cp.version.empty()) { auto v = Semver::parse(cp.version); @@ -284,13 +301,13 @@ void SystemPresetsCache::apply(PresetBundle& bundle) const } DynamicPrintConfig config = cp.config; Preset& p = coll.load_preset(cp.file, cp.name, std::move(config), /*select=*/false, version); - p.is_system = true; - p.is_visible = cp.is_visible; - p.alias = cp.alias; - p.renamed_from = cp.renamed_from; - p.filament_id = cp.filament_id; - p.setting_id = cp.setting_id; - p.description = cp.description; + p.is_system = true; + p.is_visible = cp.is_visible; + p.alias = cp.alias; + p.renamed_from = cp.renamed_from; + p.filament_id = cp.filament_id; + p.setting_id = cp.setting_id; + p.description = cp.description; p.m_from_orca_filament_lib = cp.m_from_orca_filament_lib; if (!cp.vendor_id.empty()) { auto it = bundle.vendors.find(cp.vendor_id); @@ -307,9 +324,12 @@ void SystemPresetsCache::apply(PresetBundle& bundle) const apply_col(sla_print_presets, bundle.sla_prints, false); apply_col(sla_material_presets, bundle.sla_materials, false); + // config_maps and filament_id_maps are derived from ORCA_FILAMENT_LIBRARY. + // If that vendor is in skip_vendor_ids it will be re-loaded from JSON and will + // overwrite these; otherwise the cached values are still valid. bundle.m_config_maps = config_maps; bundle.m_filament_id_maps = filament_id_maps; - // Caller must invoke bundle.update_system_maps() after this (it is private to PresetBundle). + // Caller must invoke bundle.update_system_maps() after all loading is done. } bool SystemPresetsCache::load(const std::string& path) diff --git a/src/libslic3r/PresetBundleCache.hpp b/src/libslic3r/PresetBundleCache.hpp index b0a178eb29..6996357b71 100644 --- a/src/libslic3r/PresetBundleCache.hpp +++ b/src/libslic3r/PresetBundleCache.hpp @@ -1,7 +1,8 @@ -#pragma once +#pragma once #include #include +#include #include #include @@ -121,8 +122,14 @@ struct SystemPresetsCache { static std::string cache_path(); static std::string bundled_cache_path(); bool is_valid(const std::string& system_dir) const; + // Returns false on structural mismatch (full re-parse required). + // On true, populates out_dirty with vendor IDs whose version strings changed. + // Empty out_dirty means fully valid (cache hit, no re-parse needed). + bool get_dirty_vendors(const std::string& system_dir, std::set& out_dirty) const; void capture(const PresetBundle& bundle, const std::string& system_dir); void apply(PresetBundle& bundle) const; + // Same as apply() but skips vendors listed in skip_vendor_ids and their presets. + void apply_partial(PresetBundle& bundle, const std::set& skip_vendor_ids) const; bool load(const std::string& path); void save(const std::string& path) const; };