Optimization of ConfigBase::equals() to not create intermediate

list of modified options.

Optimization of DynamicConfig::equals(), ::diff(), ::equal()
to iterate over the two compared std::map trees instead of
first generating a list of keys and then searching for each key
in the respective map.

Optimization of PresetCollection::current_is_dirty() and ::saved_is_dirty()
to call DynamicConfig::equals() instead of ::diff().
This commit is contained in:
Vojtech Bubnik 2021-08-23 10:47:40 +02:00
parent 93566d72e5
commit 1d6ade1d9c
4 changed files with 116 additions and 14 deletions

View File

@ -426,7 +426,19 @@ void ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys
}
}
// this will *ignore* options not present in both configs
// Are the two configs equal? Ignoring options not present in both configs.
bool ConfigBase::equals(const ConfigBase &other) const
{
for (const t_config_option_key &opt_key : this->keys()) {
const ConfigOption *this_opt = this->option(opt_key);
const ConfigOption *other_opt = other.option(opt_key);
if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt)
return false;
}
return true;
}
// Returns options differing in the two configs, ignoring options not present in both configs.
t_config_option_keys ConfigBase::diff(const ConfigBase &other) const
{
t_config_option_keys diff;
@ -439,6 +451,7 @@ t_config_option_keys ConfigBase::diff(const ConfigBase &other) const
return diff;
}
// Returns options being equal in the two configs, ignoring options not present in both configs.
t_config_option_keys ConfigBase::equal(const ConfigBase &other) const
{
t_config_option_keys equal;
@ -1190,6 +1203,65 @@ t_config_option_keys StaticConfig::keys() const
return keys;
}
// Iterate over the pairs of options with equal keys, call the fn.
// Returns true on early exit by fn().
template<typename Fn>
static inline bool dynamic_config_iterate(const DynamicConfig &lhs, const DynamicConfig &rhs, Fn fn)
{
std::map<t_config_option_key, std::unique_ptr<ConfigOption>>::const_iterator i = lhs.cbegin();
std::map<t_config_option_key, std::unique_ptr<ConfigOption>>::const_iterator j = rhs.cbegin();
while (i != lhs.cend() && j != rhs.cend())
if (i->first < j->first)
++ i;
else if (i->first > j->first)
++ j;
else {
assert(i->first == j->first);
if (fn(i->first, i->second.get(), j->second.get()))
// Early exit by fn.
return true;
++ i;
++ j;
}
// Finished to the end.
return false;
}
// Are the two configs equal? Ignoring options not present in both configs.
bool DynamicConfig::equals(const DynamicConfig &other) const
{
return ! dynamic_config_iterate(*this, other,
[](const t_config_option_key & /* key */, const ConfigOption *l, const ConfigOption *r) { return *l != *r; });
}
// Returns options differing in the two configs, ignoring options not present in both configs.
t_config_option_keys DynamicConfig::diff(const DynamicConfig &other) const
{
t_config_option_keys diff;
dynamic_config_iterate(*this, other,
[&diff](const t_config_option_key &key, const ConfigOption *l, const ConfigOption *r) {
if (*l != *r)
diff.emplace_back(key);
// Continue iterating.
return false;
});
return diff;
}
// Returns options being equal in the two configs, ignoring options not present in both configs.
t_config_option_keys DynamicConfig::equal(const DynamicConfig &other) const
{
t_config_option_keys equal;
dynamic_config_iterate(*this, other,
[&equal](const t_config_option_key &key, const ConfigOption *l, const ConfigOption *r) {
if (*l == *r)
equal.emplace_back(key);
// Continue iterating.
return false;
});
return equal;
}
}
#include <cereal/types/polymorphic.hpp>

View File

@ -1893,8 +1893,8 @@ public:
// The configuration definition is static: It does not carry the actual configuration values,
// but it carries the defaults of the configuration values.
ConfigBase() {}
~ConfigBase() override {}
ConfigBase() = default;
~ConfigBase() override = default;
// Virtual overridables:
public:
@ -1953,8 +1953,11 @@ public:
// An UnknownOptionException is thrown in case some option keys are not defined by this->def(),
// or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set.
void apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false);
bool equals(const ConfigBase &other) const { return this->diff(other).empty(); }
// Are the two configs equal? Ignoring options not present in both configs.
bool equals(const ConfigBase &other) const;
// Returns options differing in the two configs, ignoring options not present in both configs.
t_config_option_keys diff(const ConfigBase &other) const;
// Returns options being equal in the two configs, ignoring options not present in both configs.
t_config_option_keys equal(const ConfigBase &other) const;
std::string opt_serialize(const t_config_option_key &opt_key) const;
@ -2022,12 +2025,12 @@ private:
class DynamicConfig : public virtual ConfigBase
{
public:
DynamicConfig() {}
DynamicConfig() = default;
DynamicConfig(const DynamicConfig &rhs) { *this = rhs; }
DynamicConfig(DynamicConfig &&rhs) noexcept : options(std::move(rhs.options)) { rhs.options.clear(); }
explicit DynamicConfig(const ConfigBase &rhs, const t_config_option_keys &keys);
explicit DynamicConfig(const ConfigBase& rhs) : DynamicConfig(rhs, rhs.keys()) {}
virtual ~DynamicConfig() override { clear(); }
virtual ~DynamicConfig() override = default;
// Copy a content of one DynamicConfig to another DynamicConfig.
// If rhs.def() is not null, then it has to be equal to this->def().
@ -2144,6 +2147,13 @@ public:
}
}
// Are the two configs equal? Ignoring options not present in both configs.
bool equals(const DynamicConfig &other) const;
// Returns options differing in the two configs, ignoring options not present in both configs.
t_config_option_keys diff(const DynamicConfig &other) const;
// Returns options being equal in the two configs, ignoring options not present in both configs.
t_config_option_keys equal(const DynamicConfig &other) const;
std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return this->option<ConfigOptionString>(opt_key, create)->value; }
const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast<DynamicConfig*>(this)->opt_string(opt_key); }
std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionStrings>(opt_key)->get_at(idx); }

View File

@ -1194,21 +1194,38 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi
return diff;
}
static constexpr const std::initializer_list<const char*> optional_keys { "compatible_prints", "compatible_printers" };
bool PresetCollection::is_dirty(const Preset *edited, const Preset *reference)
{
if (edited != nullptr && reference != nullptr) {
// Only compares options existing in both configs.
if (! reference->config.equals(edited->config))
return true;
// The "compatible_printers" option key is handled differently from the others:
// It is not mandatory. If the key is missing, it means it is compatible with any printer.
// If the key exists and it is empty, it means it is compatible with no printer.
for (auto &opt_key : optional_keys)
if (reference->config.has(opt_key) != edited->config.has(opt_key))
return true;
}
return false;
}
std::vector<std::string> PresetCollection::dirty_options(const Preset *edited, const Preset *reference, const bool deep_compare /*= false*/)
{
std::vector<std::string> changed;
if (edited != nullptr && reference != nullptr) {
// Only compares options existing in both configs.
changed = deep_compare ?
deep_diff(edited->config, reference->config) :
reference->config.diff(edited->config);
// The "compatible_printers" option key is handled differently from the others:
// It is not mandatory. If the key is missing, it means it is compatible with any printer.
// If the key exists and it is empty, it means it is compatible with no printer.
std::initializer_list<const char*> optional_keys { "compatible_prints", "compatible_printers" };
for (auto &opt_key : optional_keys) {
for (auto &opt_key : optional_keys)
if (reference->config.has(opt_key) != edited->config.has(opt_key))
changed.emplace_back(opt_key);
}
}
return changed;
}

View File

@ -463,7 +463,8 @@ public:
size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); }
// Compare the content of get_selected_preset() with get_edited_preset() configs, return true if they differ.
bool current_is_dirty() const { return ! this->current_dirty_options().empty(); }
bool current_is_dirty() const
{ return is_dirty(&this->get_edited_preset(), &this->get_selected_preset()); }
// Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ.
std::vector<std::string> current_dirty_options(const bool deep_compare = false) const
{ return dirty_options(&this->get_edited_preset(), &this->get_selected_preset(), deep_compare); }
@ -472,10 +473,11 @@ public:
{ return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent(), deep_compare); }
// Compare the content of get_saved_preset() with get_edited_preset() configs, return true if they differ.
bool saved_is_dirty() const { return !this->saved_dirty_options().empty(); }
bool saved_is_dirty() const
{ return is_dirty(&this->get_edited_preset(), &this->get_saved_preset()); }
// Compare the content of get_saved_preset() with get_edited_preset() configs, return the list of keys where they differ.
std::vector<std::string> saved_dirty_options(const bool deep_compare = false) const
{ return dirty_options(&this->get_edited_preset(), &this->get_saved_preset(), deep_compare); }
// std::vector<std::string> saved_dirty_options() const
// { return dirty_options(&this->get_edited_preset(), &this->get_saved_preset(), /* deep_compare */ false); }
// Copy edited preset into saved preset.
void update_saved_preset_from_current_preset() { m_saved_preset = m_edited_preset; }
@ -552,7 +554,8 @@ private:
size_t update_compatible_internal(const PresetWithVendorProfile &active_printer, const PresetWithVendorProfile *active_print, PresetSelectCompatibleType unselect_if_incompatible);
public:
static std::vector<std::string> dirty_options(const Preset *edited, const Preset *reference, const bool is_printer_type = false);
static bool is_dirty(const Preset *edited, const Preset *reference);
static std::vector<std::string> dirty_options(const Preset *edited, const Preset *reference, const bool deep_compare = false);
private:
// Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER.
Preset::Type m_type;