diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index c64c7fed65..c541b81339 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -42,7 +42,14 @@ add_component_dir (l10n ) add_component_dir (settings - settings parser + categories + parser + sanitizer + sanitizerimpl + settings + settingvalue + shadermanager + values ) add_component_dir (bsa diff --git a/components/settings/sanitizer.hpp b/components/settings/sanitizer.hpp new file mode 100644 index 0000000000..f8ef7ba3df --- /dev/null +++ b/components/settings/sanitizer.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_COMPONENTS_SETTINGS_SANITIZER_H +#define OPENMW_COMPONENTS_SETTINGS_SANITIZER_H + +namespace Settings +{ + template + struct Sanitizer + { + virtual ~Sanitizer() = default; + + virtual T apply(const T& value) const = 0; + }; +} + +#endif diff --git a/components/settings/sanitizerimpl.cpp b/components/settings/sanitizerimpl.cpp new file mode 100644 index 0000000000..a3eaf8cbe0 --- /dev/null +++ b/components/settings/sanitizerimpl.cpp @@ -0,0 +1,205 @@ +#include "sanitizerimpl.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Settings +{ + namespace + { + template + struct Max final : Sanitizer + { + T mMax; + + explicit Max(const T& max) + : mMax(max) + { + } + + T apply(const T& value) const override { return std::max(value, mMax); } + }; + + template + struct MaxStrict final : Sanitizer + { + static_assert(std::is_floating_point_v); + + T mMax; + + explicit MaxStrict(const T& max) + : mMax(std::nextafter(max, std::numeric_limits::max())) + { + } + + T apply(const T& value) const override { return std::max(value, mMax); } + }; + + template <> + struct MaxStrict final : Sanitizer + { + osg::Vec3f mMax; + + explicit MaxStrict(const osg::Vec3f& max) + : mMax(std::nextafter(max.x(), std::numeric_limits::max()), + std::nextafter(max.y(), std::numeric_limits::max()), + std::nextafter(max.z(), std::numeric_limits::max())) + { + } + + osg::Vec3f apply(const osg::Vec3f& value) const override + { + return osg::Vec3f( + std::max(value.x(), mMax.x()), std::max(value.y(), mMax.y()), std::max(value.z(), mMax.z())); + } + }; + + template + struct Clamp final : Sanitizer + { + T mMin; + T mMax; + + explicit Clamp(const T& min, const T& max) + : mMin(min) + , mMax(max) + { + } + + T apply(const T& value) const override { return std::clamp(value, mMin, mMax); } + }; + + template + auto getPrev(const T& value) -> std::enable_if_t, T> + { + assert(value > -std::numeric_limits::max()); + return std::nextafter(value, -std::numeric_limits::max()); + } + + template + struct ClampStrictMax final : Sanitizer + { + T mMin; + T mMax; + + explicit ClampStrictMax(const T& min, const T& max) + : mMin(min) + , mMax(getPrev(max)) + { + } + + T apply(const T& value) const override { return std::clamp(value, mMin, mMax); } + }; + + template + struct Enum final : Sanitizer + { + std::vector mValues; + + explicit Enum(std::initializer_list value) + : mValues(std::make_move_iterator(value.begin()), std::make_move_iterator(value.end())) + { + } + + T apply(const T& value) const override + { + if (std::find(mValues.begin(), mValues.end(), value) == mValues.end()) + { + std::ostringstream message; + message << "Invalid enum value: " << value; + throw std::runtime_error(message.str()); + } + return value; + } + }; + + template + struct EqualOrMax final : Sanitizer + { + T mEqual; + T mMax; + + explicit EqualOrMax(const T& equal, const T& max) + : mEqual(equal) + , mMax(max) + { + } + + T apply(const T& value) const override + { + if (value == mEqual) + return value; + return std::max(value, mMax); + } + }; + } + + std::unique_ptr> makeMaxSanitizerFloat(float max) + { + return std::make_unique>(max); + } + + std::unique_ptr> makeMaxSanitizerInt(int max) + { + return std::make_unique>(max); + } + + std::unique_ptr> makeMaxSanitizerSize(std::size_t max) + { + return std::make_unique>(max); + } + + std::unique_ptr> makeMaxSanitizerUInt64(std::uint64_t max) + { + return std::make_unique>(max); + } + + std::unique_ptr> makeMaxStrictSanitizerFloat(float max) + { + return std::make_unique>(max); + } + + std::unique_ptr> makeMaxStrictSanitizerVec3f(const osg::Vec3f& max) + { + return std::make_unique>(max); + } + + std::unique_ptr> makeClampSanitizerFloat(float min, float max) + { + return std::make_unique>(min, max); + } + + std::unique_ptr> makeClampSanitizerInt(int min, int max) + { + return std::make_unique>(min, max); + } + + std::unique_ptr> makeClampStrictMaxSanitizerFloat(float min, float max) + { + return std::make_unique>(min, max); + } + + std::unique_ptr> makeEnumSanitizerInt(std::initializer_list values) + { + return std::make_unique>(values); + } + + std::unique_ptr> makeEnumSanitizerString(std::initializer_list values) + { + return std::make_unique>(values); + } + + std::unique_ptr> makeEqualOrMaxSanitizerFloat(float equal, float max) + { + return std::make_unique>(equal, max); + } +} diff --git a/components/settings/sanitizerimpl.hpp b/components/settings/sanitizerimpl.hpp new file mode 100644 index 0000000000..7327633bc8 --- /dev/null +++ b/components/settings/sanitizerimpl.hpp @@ -0,0 +1,40 @@ +#ifndef OPENMW_COMPONENTS_SETTINGS_SANITIZERIMPL_H +#define OPENMW_COMPONENTS_SETTINGS_SANITIZERIMPL_H + +#include "sanitizer.hpp" + +#include + +#include +#include +#include +#include + +namespace Settings +{ + std::unique_ptr> makeMaxSanitizerFloat(float max); + + std::unique_ptr> makeMaxSanitizerInt(int max); + + std::unique_ptr> makeMaxSanitizerSize(std::size_t max); + + std::unique_ptr> makeMaxSanitizerUInt64(std::uint64_t max); + + std::unique_ptr> makeMaxStrictSanitizerFloat(float max); + + std::unique_ptr> makeMaxStrictSanitizerVec3f(const osg::Vec3f& max); + + std::unique_ptr> makeClampSanitizerFloat(float min, float max); + + std::unique_ptr> makeClampSanitizerInt(int min, int max); + + std::unique_ptr> makeClampStrictMaxSanitizerFloat(float min, float max); + + std::unique_ptr> makeEnumSanitizerInt(std::initializer_list values); + + std::unique_ptr> makeEnumSanitizerString(std::initializer_list values); + + std::unique_ptr> makeEqualOrMaxSanitizerFloat(float equal, float max); +} + +#endif diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index b389c63b6a..a99038cf08 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -1,5 +1,6 @@ #include "settings.hpp" #include "parser.hpp" +#include "values.hpp" #include #include @@ -79,11 +80,19 @@ namespace Settings return doubleValue; } #endif + template + std::string serialize(const T& value) + { + std::ostringstream stream; + stream << value; + return stream.str(); + } } CategorySettingValueMap Manager::mDefaultSettings = CategorySettingValueMap(); CategorySettingValueMap Manager::mUserSettings = CategorySettingValueMap(); CategorySettingVector Manager::mChangedSettings = CategorySettingVector(); + std::set> Manager::sInitialized; void Manager::clear() { @@ -136,6 +145,12 @@ namespace Settings if (std::filesystem::exists(settingspath)) parser.loadSettingsFile(settingspath, mUserSettings, false, false); + Settings::Values::init(); + + for (const auto& [key, value] : mDefaultSettings) + if (!sInitialized.contains(key)) + throw std::runtime_error("Default setting [" + key.first + "] " + key.second + " is not initialized"); + return settingspath; } @@ -203,6 +218,21 @@ namespace Settings return parseNumberFromSetting(getString(setting, category), setting, category); } + unsigned Manager::getUnsigned(std::string_view setting, std::string_view category) + { + return parseNumberFromSetting(getString(setting, category), setting, category); + } + + unsigned long Manager::getUnsignedLong(std::string_view setting, std::string_view category) + { + return parseNumberFromSetting(getString(setting, category), setting, category); + } + + unsigned long long Manager::getUnsignedLongLong(std::string_view setting, std::string_view category) + { + return parseNumberFromSetting(getString(setting, category), setting, category); + } + bool Manager::getBool(std::string_view setting, std::string_view category) { const std::string& string = getString(setting, category); @@ -360,4 +390,59 @@ namespace Settings } } + void Manager::set(std::string_view setting, std::string_view category, int value) + { + setInt(setting, category, value); + } + + void Manager::set(std::string_view setting, std::string_view category, unsigned value) + { + setString(setting, category, serialize(value)); + } + + void Manager::set(std::string_view setting, std::string_view category, unsigned long value) + { + setString(setting, category, serialize(value)); + } + + void Manager::set(std::string_view setting, std::string_view category, unsigned long long value) + { + setString(setting, category, serialize(value)); + } + + void Manager::set(std::string_view setting, std::string_view category, float value) + { + setFloat(setting, category, value); + } + + void Manager::set(std::string_view setting, std::string_view category, double value) + { + setDouble(setting, category, value); + } + + void Manager::set(std::string_view setting, std::string_view category, const std::string& value) + { + setString(setting, category, value); + } + + void Manager::set(std::string_view setting, std::string_view category, bool value) + { + setBool(setting, category, value); + } + + void Manager::set(std::string_view setting, std::string_view category, const osg::Vec2f& value) + { + setVector2(setting, category, value); + } + + void Manager::set(std::string_view setting, std::string_view category, const osg::Vec3f& value) + { + setVector3(setting, category, value); + } + + void Manager::recordInit(std::string_view setting, std::string_view category) + { + sInitialized.emplace(category, setting); + } + } diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index 5ca5aee461..5f3fb65aba 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -3,6 +3,7 @@ #include "categories.hpp" +#include #include #include #include @@ -61,6 +62,9 @@ namespace Settings static int getInt(std::string_view setting, std::string_view category); static std::uint64_t getUInt64(std::string_view setting, std::string_view category); static std::size_t getSize(std::string_view setting, std::string_view category); + static unsigned getUnsigned(std::string_view setting, std::string_view category); + static unsigned long getUnsignedLong(std::string_view setting, std::string_view category); + static unsigned long long getUnsignedLongLong(std::string_view setting, std::string_view category); static float getFloat(std::string_view setting, std::string_view category); static double getDouble(std::string_view setting, std::string_view category); static const std::string& getString(std::string_view setting, std::string_view category); @@ -69,6 +73,13 @@ namespace Settings static osg::Vec2f getVector2(std::string_view setting, std::string_view category); static osg::Vec3f getVector3(std::string_view setting, std::string_view category); + template + static T get(std::string_view setting, std::string_view category) + { + recordInit(setting, category); + return getImpl(setting, category); + } + static void setInt(std::string_view setting, std::string_view category, int value); static void setUInt64(std::string_view setting, std::string_view category, std::uint64_t value); static void setFloat(std::string_view setting, std::string_view category, float value); @@ -79,8 +90,86 @@ namespace Settings static void setBool(std::string_view setting, std::string_view category, bool value); static void setVector2(std::string_view setting, std::string_view category, osg::Vec2f value); static void setVector3(std::string_view setting, std::string_view category, osg::Vec3f value); + + static void set(std::string_view setting, std::string_view category, int value); + static void set(std::string_view setting, std::string_view category, unsigned value); + static void set(std::string_view setting, std::string_view category, unsigned long value); + static void set(std::string_view setting, std::string_view category, unsigned long long value); + static void set(std::string_view setting, std::string_view category, float value); + static void set(std::string_view setting, std::string_view category, double value); + static void set(std::string_view setting, std::string_view category, const std::string& value); + static void set(std::string_view setting, std::string_view category, bool value); + static void set(std::string_view setting, std::string_view category, const osg::Vec2f& value); + static void set(std::string_view setting, std::string_view category, const osg::Vec3f& value); + + private: + static std::set> sInitialized; + + template + static T getImpl(std::string_view setting, std::string_view category); + + static void recordInit(std::string_view setting, std::string_view category); }; + template <> + inline int Manager::getImpl(std::string_view setting, std::string_view category) + { + return getInt(setting, category); + } + + template <> + inline unsigned Manager::getImpl(std::string_view setting, std::string_view category) + { + return getUnsigned(setting, category); + } + + template <> + inline unsigned long Manager::getImpl(std::string_view setting, std::string_view category) + { + return getUnsignedLong(setting, category); + } + + template <> + inline unsigned long long Manager::getImpl(std::string_view setting, std::string_view category) + { + return getUnsignedLongLong(setting, category); + } + + template <> + inline float Manager::getImpl(std::string_view setting, std::string_view category) + { + return getFloat(setting, category); + } + + template <> + inline double Manager::getImpl(std::string_view setting, std::string_view category) + { + return getDouble(setting, category); + } + + template <> + inline std::string Manager::getImpl(std::string_view setting, std::string_view category) + { + return getString(setting, category); + } + + template <> + inline bool Manager::getImpl(std::string_view setting, std::string_view category) + { + return getBool(setting, category); + } + + template <> + inline osg::Vec2f Manager::getImpl(std::string_view setting, std::string_view category) + { + return getVector2(setting, category); + } + + template <> + inline osg::Vec3f Manager::getImpl(std::string_view setting, std::string_view category) + { + return getVector3(setting, category); + } } #endif // COMPONENTS_SETTINGS_H diff --git a/components/settings/settingvalue.hpp b/components/settings/settingvalue.hpp new file mode 100644 index 0000000000..e84f014c9b --- /dev/null +++ b/components/settings/settingvalue.hpp @@ -0,0 +1,70 @@ +#ifndef OPENMW_COMPONENTS_SETTINGS_SETTINGVALUE_H +#define OPENMW_COMPONENTS_SETTINGS_SETTINGVALUE_H + +#include "sanitizer.hpp" +#include "settings.hpp" + +#include "components/debug/debuglog.hpp" + +#include + +#include +#include +#include + +namespace Settings +{ + template + class SettingValue + { + public: + explicit SettingValue( + std::string_view category, std::string_view name, std::unique_ptr>&& sanitizer = nullptr) + : mCategory(category) + , mName(name) + , mSanitizer(std::move(sanitizer)) + , mValue(sanitize(Settings::Manager::get(name, category))) + { + } + + const T& get() const { return mValue; } + + operator const T&() const { return mValue; } + + void set(const T& value) + { + if (mValue == value) + return; + mValue = sanitize(value); + Settings::Manager::set(mName, mCategory, mValue); + } + + private: + const std::string_view mCategory; + const std::string_view mName; + const std::unique_ptr> mSanitizer; + T mValue{}; + + T sanitize(const T& value) const + { + if (mSanitizer == nullptr) + return value; + try + { + T sanitizedValue = mSanitizer->apply(value); + if (sanitizedValue != value) + Log(Debug::Warning) << "Setting [" << mCategory << "] " << mName + << " value is out of allowed values set: " << value << ", sanitized to " + << sanitizedValue; + return sanitizedValue; + } + catch (const std::exception& e) + { + throw std::runtime_error("Invalid setting [" + std::string(mCategory) + "] " + std::string(mName) + + " value: " + std::string(e.what())); + } + } + }; +} + +#endif diff --git a/components/settings/values.cpp b/components/settings/values.cpp new file mode 100644 index 0000000000..668a85c8fd --- /dev/null +++ b/components/settings/values.cpp @@ -0,0 +1,12 @@ +#include "values.hpp" + +namespace Settings +{ + Values* Values::sValues = nullptr; + + void Values::init() + { + static Values values; + Values::sValues = &values; + } +} diff --git a/components/settings/values.hpp b/components/settings/values.hpp new file mode 100644 index 0000000000..9614d44c01 --- /dev/null +++ b/components/settings/values.hpp @@ -0,0 +1,33 @@ +#ifndef OPENMW_COMPONENTS_SETTINGS_VALUES_H +#define OPENMW_COMPONENTS_SETTINGS_VALUES_H + +#include "sanitizerimpl.hpp" +#include "settingvalue.hpp" + +namespace Settings +{ + class Values + { + public: + static void init(); + + private: + static Values* sValues; + + friend const Values& values(); + + friend Values& valuesMutable(); + }; + + inline const Values& values() + { + return *Values::sValues; + } + + inline Values& valuesMutable() + { + return *Values::sValues; + } +} + +#endif