mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-25 09:50:59 +00:00
195 lines
9.7 KiB
C++
195 lines
9.7 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 names of the
|
|
// preset it inherits and the presets it includes, and the parse metadata —
|
|
// everything the parse phase of load_vendor_configs_from_json extracts and
|
|
// nothing it derives. Inheritance and includes are 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::vector<std::string> includes; // layered under config_src, in this order
|
|
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_
|