mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-16 21:42:43 +00:00
* Add caching system for presets * Removing user\bundle serialization and keeping it only for system presets * Integrate caching into WebGuideDialog which speeds up time of SetupWizzard and PrinterSelection dialog * Add CI\CD step to prepare cache file in ahead of time so user does not need to wait * Add partial cache generation when only one of the vendros is changed to speed up recalculation time * Handle corrupted files * Add cache to GuideDialog as previos version didn't work as expected * Add inspecting tool and fix CI cache generation * Generate cache per vendor * Simplify code by mergin it in PresetBundle * Simplify code a bit more * Add cereal serialize() to VendorProfile, PrinterModel, Preset, and Semver * Remove CachedPrinterModel/VendorProfile/Preset mirror structs from VendorCache * Fix use-after-free in CallAfter lambda; replace raw thread pointer with unique_ptr * Use get_vendor_cache_key() to match cache keys written by the app * Remove BOM added by VSC * Skip invalid vendors * Remove leftover cache file * Fix build for windows arm64 * Revert json cache back * Update check for stale cache * Serealize all value fields for Preset class to minimize regression later * Minimize field duplication by moving Cache thing into PresetBundle * Add tests for Cache system * Add a bit more tests * Merge branch 'main' into feature/cache_profiles_and_optimize_loading_speed * Rvert from per-verndor to single cache file Replace N per-vendor .cache files with a single system_presets.cache that holds all vendors and presets in one serialized blob. Cache load is now all-or-nothing: on hit all vendors are applied from the bundle (sub-second); on miss all vendors are parsed from JSON and a fresh bundle is written to the user cache dir. Invalidation is driven by bundle_key - a sorted concatenation of all vendor JSON version strings. Any vendor update invalidates the whole cache and triggers re-parse on next launch. Guide wizard (WebGuideDialog) loads the bundled cache into a plain PresetBundle instead of a separate VendorGuideData struct, removing the duplicate data model. generate_system_cache simplified from a per-vendor loop to a single save_system_presets_cache() call producing one output file. * Transfer all Preset fields from cache via move assignmet apply_vendor_preset_group was copying fields manually and missed bundle_id, user_id, base_id, sync_info, updated_time, key_values, ini_str. Replace field-by-field copy with move assignment of the fully-deserialized Preset, then restore the vendor pointer which is excluded from serialization. * Ignore cache for future * Remove not used files * Ship one preset cache per vendor in place of the profile JSONs Each vendor's system presets serialize into a single <vendor>.opc built at package time, and a shipped build carries that file alone — the profile JSON and its sub-file tree are pruned. The vendor loader, the setup wizard's profile list and the resource installer all read a vendor through its cache, falling back to parsing whenever one is absent, stale or unreadable, so the cache stays an optimization and never a source of truth. Caches hold presets in source form and resolve inheritance at load, through the same code the JSON path uses. * Make the preset cache self-describing and load each vendor from the system folder alone The cached DynamicPrintConfig is keyed by name, through a per-file dictionary of the distinct opt_keys, the type each was written as, and the distinct enum value names, instead of by serialization_key_ordinal — a position assigned by declaration order at static init, where inserting one option shifts every later ordinal and the lookup then succeeds on the wrong option. Because a name-keyed payload drops the options this build cannot place rather than being rejected wholesale, the schema fingerprint goes, and with it the two fallbacks that existed only because an installed cache died on every app upgrade: the second lookup tier into resources/profiles and the parse fallback to the same place. A vendor is loaded from <data_dir>/system/ and nowhere else, as on main — which is what makes the app write its .opc files there again. * Simplify the preset cache internals after review * Use the shared temp-dir helper in the preset bundle loading test * Bound stamp string reads in the preset cache * Speed up the setup wizard with a profile-data cache The wizard's per-vendor fast path threw on vendors present only in resources, falling back to a ~29 s raw JSON scan on every open. Each vendor now loads from the directory it was found in, and the derived model/machine/filament/process catalog is cached whole in <data_dir>/cache/wizard_profile_data.json, stamped by each vendor's name and version - a fresh cache makes an open one file read, with no bundle built and no presets installed (~0.2 s vs ~2 s). * Remove debug SVG dump from a geometry test * Move the per-vendor cache file format into PresetCacheFormat * Move the vendor install helpers from PresetBundle into Utils * rename * fix flatpak * change cache version to 1 --------- Co-authored-by: SoftFever <softfeverever@gmail.com>
193 lines
9.6 KiB
C++
193 lines
9.6 KiB
C++
#ifndef slic3r_PresetCacheFormat_hpp_
|
|
#define slic3r_PresetCacheFormat_hpp_
|
|
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#include <cereal/archives/binary.hpp>
|
|
#include <cereal/types/string.hpp>
|
|
#include <cereal/types/vector.hpp>
|
|
|
|
#include "libslic3r/Config.hpp"
|
|
#include "libslic3r/Preset.hpp"
|
|
#include "libslic3r/PrintConfig.hpp"
|
|
#include "libslic3r/Semver.hpp"
|
|
|
|
namespace Slic3r {
|
|
|
|
// How the preset cache writes a DynamicPrintConfig.
|
|
//
|
|
// Not through the global cereal hooks in PrintConfig.hpp: those key an option by
|
|
// its serialization_key_ordinal, which ConfigDef::add assigns by declaration
|
|
// order at static-init time. Inserting one option into the middle of
|
|
// PrintConfig.cpp shifts every later ordinal, and the lookup on the way back in
|
|
// then SUCCEEDS on the wrong option — where the two share a type, and hundreds
|
|
// of coFloat/coBool/coInt options do, the bytes deserialize cleanly into the
|
|
// wrong key. Silently wrong print settings, no error. Those hooks are also the
|
|
// undo/redo wire format, where the process cannot change underneath them, so
|
|
// they stay as they are and the cache keys by name instead.
|
|
//
|
|
// Names are not repeated per preset. Each cache file carries one dictionary of
|
|
// the distinct opt_keys it uses, the type each was written as, and the distinct
|
|
// enum value names; an option on the wire is then a uint16 index into it plus
|
|
// its value. The dictionary is resolved to this build's option definitions once
|
|
// per file, after which reading an option is a vector index.
|
|
class CacheDictionary
|
|
{
|
|
public:
|
|
CacheDictionary();
|
|
|
|
// Index reserved in the enum table for an int the writing build could not
|
|
// name — a nullable option's nil, or a definition carrying no
|
|
// enum_keys_map. The raw int32 follows it on the wire and is loaded
|
|
// verbatim, so those values survive too.
|
|
static constexpr uint16_t ENUM_UNNAMED = 0;
|
|
|
|
// ---- writing ----
|
|
|
|
// Record every key and enum value `config` uses. Call for every config that
|
|
// will be written, before writing the dictionary.
|
|
void collect(const DynamicPrintConfig& config);
|
|
|
|
uint16_t key_index(const t_config_option_key& key) const;
|
|
// ENUM_UNNAMED for an empty name or one that was never collected.
|
|
uint16_t enum_index(const std::string& name) const;
|
|
|
|
// ---- reading ----
|
|
|
|
// The definition an index resolves to in THIS build, or nullptr where the
|
|
// key is unknown here or is now defined with a different type. A nullptr
|
|
// entry's value is still read — using type_at(idx), the type the writer
|
|
// recorded — and then dropped, which is what a JSON profile gets for an
|
|
// option this build no longer has.
|
|
const ConfigOptionDef* def_at(uint16_t idx) const { return m_defs[idx]; }
|
|
ConfigOptionType type_at(uint16_t idx) const { return ConfigOptionType(m_types[idx]); }
|
|
const std::string& enum_name_at(uint16_t idx) const { return m_enum_values[idx]; }
|
|
// m_defs, not m_keys: only load() sizes it, so this is false for every index
|
|
// on a dictionary that was collected rather than read.
|
|
bool valid_key_index(uint16_t idx) const { return size_t(idx) < m_defs.size(); }
|
|
bool valid_enum_index(uint16_t idx) const { return size_t(idx) < m_enum_values.size(); }
|
|
|
|
// The layout these two agree on is covered by CACHE_VERSION (PresetCacheFormat.cpp);
|
|
// bump it when they change.
|
|
// Throws when either table outgrew the uint16 the wire format indexes it
|
|
// with. Both are bounded by the option count (912 at the time of writing), so
|
|
// that is a build-time failure in CI, not a runtime one.
|
|
void save(cereal::BinaryOutputArchive& ar) const;
|
|
// Throws on a dictionary that cannot be indexed as written.
|
|
void load(cereal::BinaryInputArchive& ar);
|
|
|
|
private:
|
|
// Indices are uint16, so a table may hold at most this many entries.
|
|
static constexpr size_t MAX_ENTRIES = 0xFFFF;
|
|
|
|
std::vector<std::string> m_keys;
|
|
// ConfigOptionType, as written. Sixteen bits, not eight: coVectorType is
|
|
// 0x4000, so every vector type — coFloats, coEnums, coStrings — is above
|
|
// 255, and a byte would fold each one onto its scalar counterpart.
|
|
std::vector<uint16_t> m_types;
|
|
std::vector<std::string> m_enum_values; // [ENUM_UNNAMED] is always empty
|
|
|
|
// Writing.
|
|
std::unordered_map<std::string, uint16_t> m_key_index;
|
|
std::unordered_map<std::string, uint16_t> m_enum_index;
|
|
// Reading, resolved once by load().
|
|
std::vector<const ConfigOptionDef*> m_defs;
|
|
};
|
|
|
|
// One config, keyed through `dict`. Options print_config_def does not know are
|
|
// not written: nothing could give them a type on the way back in.
|
|
void save_config(cereal::BinaryOutputArchive& ar, const DynamicPrintConfig& config, const CacheDictionary& dict);
|
|
// Throws only on a payload that cannot be indexed; an option this build cannot
|
|
// place is dropped, not fatal.
|
|
void load_config(cereal::BinaryInputArchive& ar, DynamicPrintConfig& config, const CacheDictionary& dict);
|
|
// Consume one config without building it, for a reader that only wants what
|
|
// comes after.
|
|
void skip_config(cereal::BinaryInputArchive& ar, const CacheDictionary& dict);
|
|
|
|
// One preset as its JSON subfile states it: the config diff, the name of the
|
|
// preset it inherits, and the parse metadata — everything the parse phase of
|
|
// load_vendor_configs_from_json extracts and nothing it derives. Inheritance
|
|
// is resolved when the entry is installed, against whatever filament library
|
|
// is loaded then, so a cache carries no other vendor's values and no other
|
|
// vendor's update can make it stale.
|
|
// Written and read by visit_entry in PresetCacheFormat.cpp, which lists every
|
|
// field below in this order — once, for the save, the load and the name peek alike.
|
|
struct CachedPreset
|
|
{
|
|
std::string name;
|
|
std::string sub_path; // path under the vendor's directory
|
|
DynamicPrintConfig config_src; // the preset's own diff, nothing inherited
|
|
std::string inherits;
|
|
std::string description;
|
|
std::string instantiation; // "true"/"false" as stated; anything else was already counted as a parse error
|
|
std::string setting_id;
|
|
std::string filament_id;
|
|
std::vector<std::string> renamed_from;
|
|
};
|
|
|
|
// What one per-vendor cache file carries besides its stamps: the vendor profile
|
|
// map, the presets in source form, and how many errors their parse counted.
|
|
struct VendorCacheData
|
|
{
|
|
VendorMap vendors;
|
|
std::vector<CachedPreset> process_entries;
|
|
std::vector<CachedPreset> filament_entries;
|
|
std::vector<CachedPreset> machine_entries;
|
|
uint64_t parse_errors = 0;
|
|
};
|
|
|
|
// A per-vendor preset cache file (<vendor>.opc): a 20-byte header (magic, format
|
|
// version, body size, CRC) framing one cereal body — stamps (format version,
|
|
// vendor name, vendor profile version), the option dictionary, then the
|
|
// VendorCacheData. Everything about those bytes lives here; when a vendor is
|
|
// served from its cache, and how entries install into a bundle, is
|
|
// PresetBundle's business.
|
|
class VendorCacheFile
|
|
{
|
|
public:
|
|
// Save one vendor (vendor_name at vendor_version). False when the file
|
|
// could not be written whole.
|
|
static bool save(const std::string& path, const std::string& vendor_name,
|
|
const std::string& vendor_version, const VendorCacheData& data);
|
|
|
|
// Read a whole cache into `data`. False — with `data` in an unspecified
|
|
// state — unless the file is a cache this build wrote, its CRC holds, it
|
|
// names this vendor, it was built from a vendor profile at least as new as
|
|
// `expected_vendor_version`, and it carries its own vendor profile. An
|
|
// invalid expected version (a profile whose version
|
|
// cannot be judged) is never served from cache; Semver::inf() (no profile
|
|
// beside the cache at all) accepts whatever is cached.
|
|
static bool load(const std::string& path, const std::string& expected_vendor_name,
|
|
const Semver& expected_vendor_version, VendorCacheData& data);
|
|
|
|
// Read the profile version a cache was stamped with, without deserializing
|
|
// its presets. Empty if the file is unreadable, not a cache this build
|
|
// understands, or not this vendor's. This is how an installed vendor's
|
|
// version is known when only its cache is installed.
|
|
static std::string peek_version(const std::string& path, const std::string& expected_vendor_name);
|
|
|
|
// The profile version an installed cache can actually be served at, or an
|
|
// invalid Semver when the file is not a cache this build can read. Unlike
|
|
// peek_version this verifies the body's CRC, at the cost of reading the
|
|
// whole file: where the cache is the vendor's whole installation, "a file
|
|
// is there" is not enough to call it installed, and a vendor wrongly
|
|
// believed installed is never repaired.
|
|
static Semver usable_version(const std::string& path, const std::string& expected_vendor_name);
|
|
|
|
// Whether a cache carries a preset of `type` under `preset_name`, without
|
|
// installing any of them. False when the file is not a cache this build can
|
|
// read. The three kinds are written in one stream, so reaching the machines
|
|
// means reading past the processes and filaments — their configs are consumed
|
|
// and dropped rather than built. This is how a build that ships caches instead
|
|
// of preset JSONs answers "which vendor carries this preset?".
|
|
static bool carries_preset(const std::string& path, const std::string& vendor_name,
|
|
Preset::Type type, const std::string& preset_name);
|
|
};
|
|
|
|
} // namespace Slic3r
|
|
|
|
#endif // slic3r_PresetCacheFormat_hpp_
|