mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 09:50:06 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			443 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			443 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include <scwx/qt/settings/settings_variable.hpp>
 | |
| #include <scwx/util/logger.hpp>
 | |
| 
 | |
| #include <boost/json.hpp>
 | |
| #include <boost/unordered/unordered_flat_map.hpp>
 | |
| #include <boost/uuid/random_generator.hpp>
 | |
| #include <fmt/ostream.h>
 | |
| 
 | |
| namespace scwx
 | |
| {
 | |
| namespace qt
 | |
| {
 | |
| namespace settings
 | |
| {
 | |
| 
 | |
| static const std::string logPrefix_ = "scwx::qt::settings::settings_variable";
 | |
| static const auto        logger_    = scwx::util::Logger::Create(logPrefix_);
 | |
| 
 | |
| template<class T>
 | |
| class SettingsVariable<T>::Impl
 | |
| {
 | |
| public:
 | |
|    explicit Impl() {}
 | |
|    ~Impl() {}
 | |
| 
 | |
|    T                             value_ {};
 | |
|    T                             default_ {};
 | |
|    std::optional<T>              staged_ {};
 | |
|    std::optional<T>              minimum_ {};
 | |
|    std::optional<T>              maximum_ {};
 | |
|    std::function<T(const T&)>    transform_ {};
 | |
|    std::function<bool(const T&)> validator_ {nullptr};
 | |
| 
 | |
|    boost::unordered_flat_map<boost::uuids::uuid, ValueCallbackFunction>
 | |
|       valueChangedCallbackFunctions_ {};
 | |
|    boost::unordered_flat_map<boost::uuids::uuid, ValueCallbackFunction>
 | |
|       valueStagedCallbackFunctions_ {};
 | |
| };
 | |
| 
 | |
| template<class T>
 | |
| SettingsVariable<T>::SettingsVariable(const std::string& name) :
 | |
|     SettingsVariableBase(name), p(std::make_unique<Impl>())
 | |
| {
 | |
| }
 | |
| template<class T>
 | |
| SettingsVariable<T>::~SettingsVariable() = default;
 | |
| 
 | |
| template<class T>
 | |
| SettingsVariable<T>::SettingsVariable(SettingsVariable&&) noexcept = default;
 | |
| template<class T>
 | |
| SettingsVariable<T>&
 | |
| SettingsVariable<T>::operator=(SettingsVariable&&) noexcept = default;
 | |
| 
 | |
| template<class T>
 | |
| inline auto FormatParameter(const T& value)
 | |
| {
 | |
|    if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T> ||
 | |
|                  std::is_same_v<T, std::string>)
 | |
|    {
 | |
|       return value;
 | |
|    }
 | |
|    else
 | |
|    {
 | |
|       return fmt::join(value, ", ");
 | |
|    }
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| bool SettingsVariable<T>::IsDefault() const
 | |
| {
 | |
|    return p->value_ == p->default_;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| bool SettingsVariable<T>::IsDefaultStaged() const
 | |
| {
 | |
|    return p->staged_.value_or(p->value_) == p->default_;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| T SettingsVariable<T>::GetValue() const
 | |
| {
 | |
|    return p->value_;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| bool SettingsVariable<T>::SetValue(const T& value)
 | |
| {
 | |
|    bool validated = false;
 | |
| 
 | |
|    if (Validate(value))
 | |
|    {
 | |
|       p->value_ = (p->transform_ != nullptr) ? p->transform_(value) : value;
 | |
|       validated = true;
 | |
| 
 | |
|       changed_signal()();
 | |
|       for (auto& callback : p->valueChangedCallbackFunctions_)
 | |
|       {
 | |
|          callback.second(p->value_);
 | |
|       }
 | |
| 
 | |
|       staged_signal()();
 | |
|       for (auto& callback : p->valueStagedCallbackFunctions_)
 | |
|       {
 | |
|          callback.second(p->value_);
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    return validated;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| bool SettingsVariable<T>::SetValueOrDefault(const T& value)
 | |
| {
 | |
|    bool validated = false;
 | |
| 
 | |
|    if (Validate(value))
 | |
|    {
 | |
|       p->value_ = (p->transform_ != nullptr) ? p->transform_(value) : value;
 | |
|       validated = true;
 | |
|    }
 | |
|    else if (p->minimum_.has_value() && value < p->minimum_)
 | |
|    {
 | |
|       logger_->warn("{0} less than minimum ({1} < {2}), setting to: {2}",
 | |
|                     name(),
 | |
|                     FormatParameter<T>(value),
 | |
|                     FormatParameter<T>(*p->minimum_));
 | |
|       p->value_ = *p->minimum_;
 | |
|    }
 | |
|    else if (p->maximum_.has_value() && value > p->maximum_)
 | |
|    {
 | |
|       logger_->warn("{0} greater than maximum ({1} > {2}), setting to: {2}",
 | |
|                     name(),
 | |
|                     FormatParameter<T>(value),
 | |
|                     FormatParameter<T>(*p->maximum_));
 | |
|       p->value_ = *p->maximum_;
 | |
|    }
 | |
|    else
 | |
|    {
 | |
|       logger_->warn("{} validation failed ({}), setting to default: {}",
 | |
|                     name(),
 | |
|                     FormatParameter<T>(value),
 | |
|                     FormatParameter<T>(p->default_));
 | |
|       p->value_ = p->default_;
 | |
|    }
 | |
| 
 | |
|    changed_signal()();
 | |
|    for (auto& callback : p->valueChangedCallbackFunctions_)
 | |
|    {
 | |
|       callback.second(p->value_);
 | |
|    }
 | |
| 
 | |
|    staged_signal()();
 | |
|    for (auto& callback : p->valueStagedCallbackFunctions_)
 | |
|    {
 | |
|       callback.second(p->value_);
 | |
|    }
 | |
| 
 | |
|    return validated;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::SetValueToDefault()
 | |
| {
 | |
|    p->value_ = p->default_;
 | |
| 
 | |
|    changed_signal()();
 | |
|    for (auto& callback : p->valueChangedCallbackFunctions_)
 | |
|    {
 | |
|       callback.second(p->value_);
 | |
|    }
 | |
| 
 | |
|    staged_signal()();
 | |
|    for (auto& callback : p->valueStagedCallbackFunctions_)
 | |
|    {
 | |
|       callback.second(p->value_);
 | |
|    }
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::StageDefault()
 | |
| {
 | |
|    if (p->value_ != p->default_)
 | |
|    {
 | |
|       p->staged_ = p->default_;
 | |
|    }
 | |
|    else
 | |
|    {
 | |
|       p->staged_.reset();
 | |
|    }
 | |
| 
 | |
|    staged_signal()();
 | |
|    for (auto& callback : p->valueStagedCallbackFunctions_)
 | |
|    {
 | |
|       callback.second(p->default_);
 | |
|    }
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| bool SettingsVariable<T>::StageValue(const T& value)
 | |
| {
 | |
|    bool validated = false;
 | |
| 
 | |
|    if (Validate(value))
 | |
|    {
 | |
|       T transformed = (p->transform_ != nullptr) ? p->transform_(value) : value;
 | |
| 
 | |
|       if (p->value_ != transformed)
 | |
|       {
 | |
|          p->staged_ = transformed;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|          p->staged_.reset();
 | |
|       }
 | |
| 
 | |
|       validated = true;
 | |
| 
 | |
|       staged_signal()();
 | |
|       for (auto& callback : p->valueStagedCallbackFunctions_)
 | |
|       {
 | |
|          callback.second(transformed);
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    return validated;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| bool SettingsVariable<T>::Commit()
 | |
| {
 | |
|    bool committed = false;
 | |
| 
 | |
|    if (p->staged_.has_value())
 | |
|    {
 | |
|       p->value_ = std::move(*p->staged_);
 | |
|       p->staged_.reset();
 | |
|       committed = true;
 | |
| 
 | |
|       changed_signal()();
 | |
|       for (auto& callback : p->valueChangedCallbackFunctions_)
 | |
|       {
 | |
|          callback.second(p->value_);
 | |
|       }
 | |
| 
 | |
|       staged_signal()();
 | |
|       for (auto& callback : p->valueStagedCallbackFunctions_)
 | |
|       {
 | |
|          callback.second(p->value_);
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    return committed;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::Reset()
 | |
| {
 | |
|    p->staged_.reset();
 | |
| 
 | |
|    staged_signal()();
 | |
|    for (auto& callback : p->valueStagedCallbackFunctions_)
 | |
|    {
 | |
|       callback.second(p->value_);
 | |
|    }
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| std::optional<T> SettingsVariable<T>::GetStaged() const
 | |
| {
 | |
|    return p->staged_;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| T SettingsVariable<T>::GetStagedOrValue() const
 | |
| {
 | |
|    return p->staged_.value_or(GetValue());
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| T SettingsVariable<T>::GetDefault() const
 | |
| {
 | |
|    return p->default_;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::SetDefault(const T& value)
 | |
| {
 | |
|    p->default_ = value;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::SetMinimum(const T& value)
 | |
| {
 | |
|    p->minimum_ = value;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| std::optional<T> SettingsVariable<T>::GetMinimum() const
 | |
| {
 | |
|    return p->minimum_;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::SetMaximum(const T& value)
 | |
| {
 | |
|    p->maximum_ = value;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| std::optional<T> SettingsVariable<T>::GetMaximum() const
 | |
| {
 | |
|    return p->maximum_;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::SetTransform(std::function<T(const T&)> transform)
 | |
| {
 | |
|    p->transform_ = transform;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::SetValidator(std::function<bool(const T&)> validator)
 | |
| {
 | |
|    p->validator_ = validator;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| bool SettingsVariable<T>::Validate(const T& value) const
 | |
| {
 | |
|    return (
 | |
|       (!p->minimum_.has_value() || value >= p->minimum_) && // Validate minimum
 | |
|       (!p->maximum_.has_value() || value <= p->maximum_) && // Validate maximum
 | |
|       (p->validator_ == nullptr || p->validator_(value)));  // User-validation
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| bool SettingsVariable<T>::ReadValue(const boost::json::object& json)
 | |
| {
 | |
|    const boost::json::value* jv        = json.if_contains(name());
 | |
|    bool                      validated = false;
 | |
| 
 | |
|    if (jv != nullptr)
 | |
|    {
 | |
|       try
 | |
|       {
 | |
|          validated = SetValueOrDefault(boost::json::value_to<T>(*jv));
 | |
|       }
 | |
|       catch (const std::exception& ex)
 | |
|       {
 | |
|          logger_->warn("{} is invalid ({}), setting to default: {}",
 | |
|                        name(),
 | |
|                        ex.what(),
 | |
|                        FormatParameter<T>(p->default_));
 | |
|          p->value_ = p->default_;
 | |
|       }
 | |
|    }
 | |
|    else
 | |
|    {
 | |
|       logger_->debug("{} is not present, setting to default: {}",
 | |
|                      name(),
 | |
|                      FormatParameter<T>(p->default_));
 | |
|       p->value_ = p->default_;
 | |
|    }
 | |
| 
 | |
|    changed_signal()();
 | |
|    for (auto& callback : p->valueChangedCallbackFunctions_)
 | |
|    {
 | |
|       callback.second(p->value_);
 | |
|    }
 | |
| 
 | |
|    staged_signal()();
 | |
|    for (auto& callback : p->valueStagedCallbackFunctions_)
 | |
|    {
 | |
|       callback.second(p->value_);
 | |
|    }
 | |
| 
 | |
|    return validated;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::WriteValue(boost::json::object& json) const
 | |
| {
 | |
|    json[name()] = boost::json::value_from<T&>(p->value_);
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| boost::uuids::uuid SettingsVariable<T>::RegisterValueChangedCallback(
 | |
|    ValueCallbackFunction callback)
 | |
| {
 | |
|    boost::uuids::uuid uuid = boost::uuids::random_generator()();
 | |
|    p->valueChangedCallbackFunctions_.emplace(uuid, std::move(callback));
 | |
|    return uuid;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::UnregisterValueChangedCallback(
 | |
|    boost::uuids::uuid uuid)
 | |
| {
 | |
|    p->valueChangedCallbackFunctions_.erase(uuid);
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| boost::uuids::uuid
 | |
| SettingsVariable<T>::RegisterValueStagedCallback(ValueCallbackFunction callback)
 | |
| {
 | |
|    boost::uuids::uuid uuid = boost::uuids::random_generator()();
 | |
|    p->valueStagedCallbackFunctions_.emplace(uuid, std::move(callback));
 | |
|    return uuid;
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| void SettingsVariable<T>::UnregisterValueStagedCallback(boost::uuids::uuid uuid)
 | |
| {
 | |
|    p->valueStagedCallbackFunctions_.erase(uuid);
 | |
| }
 | |
| 
 | |
| template<class T>
 | |
| bool SettingsVariable<T>::Equals(const SettingsVariableBase& o) const
 | |
| {
 | |
|    // This is only ever called with SettingsVariable<T>, so static_cast is safe
 | |
|    const SettingsVariable<T>& v = static_cast<const SettingsVariable<T>&>(o);
 | |
| 
 | |
|    // Don't compare transform or validator
 | |
|    return SettingsVariableBase::Equals(o) && //
 | |
|           p->value_ == v.p->value_ &&        //
 | |
|           p->default_ == v.p->default_ &&    //
 | |
|           p->staged_ == v.p->staged_ &&      //
 | |
|           p->minimum_ == v.p->minimum_ &&    //
 | |
|           p->maximum_ == v.p->maximum_;
 | |
| }
 | |
| 
 | |
| template class SettingsVariable<bool>;
 | |
| template class SettingsVariable<double>;
 | |
| template class SettingsVariable<std::int64_t>;
 | |
| template class SettingsVariable<std::string>;
 | |
| 
 | |
| // Containers are not to be used directly
 | |
| template class SettingsVariable<std::vector<std::int64_t>>;
 | |
| 
 | |
| } // namespace settings
 | |
| } // namespace qt
 | |
| } // namespace scwx
 | 
