#ifndef OPENMW_COMPONENTS_SETTINGS_SETTINGVALUE_H #define OPENMW_COMPONENTS_SETTINGS_SETTINGVALUE_H #include "gyroscopeaxis.hpp" #include "navmeshrendermode.hpp" #include "sanitizer.hpp" #include "settings.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace Settings { enum class SettingValueType { Bool, Int, UnsignedInt, Long, UnsignedLong, LongLong, UnsignedLongLong, Float, Double, String, Vec2f, Vec3f, CollisionShapeType, StringArray, MyGuiColour, GyroscopeAxis, NavMeshRenderMode, LightingMethod, HrtfMode, WindowMode, VSyncMode, NormalizedPath, }; template constexpr SettingValueType getSettingValueType() = delete; template <> inline constexpr SettingValueType getSettingValueType() { return SettingValueType::Bool; } template <> inline constexpr SettingValueType getSettingValueType() { return SettingValueType::Int; } template <> inline constexpr SettingValueType getSettingValueType() { return SettingValueType::UnsignedInt; } template <> inline constexpr SettingValueType getSettingValueType() { return SettingValueType::Long; } template <> inline constexpr SettingValueType getSettingValueType() { return SettingValueType::UnsignedLong; } template <> inline constexpr SettingValueType getSettingValueType() { return SettingValueType::LongLong; } template <> inline constexpr SettingValueType getSettingValueType() { return SettingValueType::UnsignedLongLong; } template <> inline constexpr SettingValueType getSettingValueType() { return SettingValueType::Float; } template <> inline constexpr SettingValueType getSettingValueType() { return SettingValueType::Double; } template <> inline constexpr SettingValueType getSettingValueType() { return SettingValueType::String; } template <> inline constexpr SettingValueType getSettingValueType() { return SettingValueType::Vec2f; } template <> inline constexpr SettingValueType getSettingValueType() { return SettingValueType::Vec3f; } template <> inline constexpr SettingValueType getSettingValueType() { return SettingValueType::CollisionShapeType; } template <> inline constexpr SettingValueType getSettingValueType>() { return SettingValueType::StringArray; } template <> inline constexpr SettingValueType getSettingValueType() { return SettingValueType::MyGuiColour; } template <> inline constexpr SettingValueType getSettingValueType() { return SettingValueType::GyroscopeAxis; } template <> inline constexpr SettingValueType getSettingValueType() { return SettingValueType::NavMeshRenderMode; } template <> inline constexpr SettingValueType getSettingValueType() { return SettingValueType::LightingMethod; } template <> inline constexpr SettingValueType getSettingValueType() { return SettingValueType::HrtfMode; } template <> inline constexpr SettingValueType getSettingValueType() { return SettingValueType::WindowMode; } template <> inline constexpr SettingValueType getSettingValueType() { return SettingValueType::VSyncMode; } template <> inline constexpr SettingValueType getSettingValueType() { return SettingValueType::NormalizedPath; } inline constexpr std::string_view getSettingValueTypeName(SettingValueType type) { switch (type) { case SettingValueType::Bool: return "bool"; case SettingValueType::Int: return "int"; case SettingValueType::UnsignedInt: return "unsigned int"; case SettingValueType::Long: return "long"; case SettingValueType::UnsignedLong: return "unsigned long"; case SettingValueType::LongLong: return "long long"; case SettingValueType::UnsignedLongLong: return "unsigned long long"; case SettingValueType::Float: return "float"; case SettingValueType::Double: return "double"; case SettingValueType::String: return "string"; case SettingValueType::Vec2f: return "vec2f"; case SettingValueType::Vec3f: return "vec3f"; case SettingValueType::CollisionShapeType: return "collision shape type"; case SettingValueType::StringArray: return "string array"; case SettingValueType::MyGuiColour: return "colour"; case SettingValueType::GyroscopeAxis: return "gyroscope axis"; case SettingValueType::NavMeshRenderMode: return "navmesh render mode"; case SettingValueType::LightingMethod: return "lighting method"; case SettingValueType::HrtfMode: return "hrtf mode"; case SettingValueType::WindowMode: return "window mode"; case SettingValueType::VSyncMode: return "vsync mode"; case SettingValueType::NormalizedPath: return "normalized path"; } return "unsupported"; } template constexpr std::string_view getSettingValueTypeName() { return getSettingValueTypeName(getSettingValueType()); } inline std::string getSettingDescription(SettingValueType type, std::string_view category, std::string_view name) { return std::string(getSettingValueTypeName(type)) + " [" + std::string(category) + "] " + std::string(name) + " setting"; } template std::string getSettingDescription(std::string_view category, std::string_view name) { return getSettingDescription(getSettingValueType(), category, name); } class Index; class BaseSettingValue { public: const SettingValueType mType; const std::string_view mCategory; const std::string_view mName; explicit BaseSettingValue( SettingValueType type, std::string_view category, std::string_view name, Index& index); BaseSettingValue(const BaseSettingValue& other) = delete; BaseSettingValue(BaseSettingValue&& other); BaseSettingValue& operator=(const BaseSettingValue& other) = delete; BaseSettingValue& operator=(BaseSettingValue&& other) = delete; private: Index& mIndex; }; template class SettingValue; class Index { public: template SettingValue* find(std::string_view category, std::string_view name) const; template SettingValue& get(std::string_view category, std::string_view name) const; void insert(BaseSettingValue* value) { if (!mValues.emplace(std::make_pair(value->mCategory, value->mName), value).second) throw std::invalid_argument("Duplicated setting definition: " + getSettingDescription(value->mType, value->mCategory, value->mName)); } void insertOrAssign(BaseSettingValue* value) { mValues.insert_or_assign(std::make_pair(value->mCategory, value->mName), value); } private: std::map, BaseSettingValue*> mValues; }; inline BaseSettingValue::BaseSettingValue( SettingValueType type, std::string_view category, std::string_view name, Index& index) : mType(type) , mCategory(category) , mName(name) , mIndex(index) { mIndex.insert(this); } inline BaseSettingValue::BaseSettingValue(BaseSettingValue&& other) : mType(other.mType) , mCategory(other.mCategory) , mName(other.mName) , mIndex(other.mIndex) { mIndex.insertOrAssign(this); } template class SettingValue final : public BaseSettingValue { public: explicit SettingValue(Index& index, std::string_view category, std::string_view name, std::unique_ptr>&& sanitizer = nullptr) : BaseSettingValue(getSettingValueType(), category, name, index) , mSanitizer(std::move(sanitizer)) , mValue(sanitize(Settings::Manager::get(mName, mCategory))) { } explicit SettingValue(Index& index, std::string_view category, std::string_view name, T&& defaultValue, std::unique_ptr>&& sanitizer = nullptr) : BaseSettingValue(getSettingValueType(), category, name, index) , mSanitizer(std::move(sanitizer)) , mDefaultValue(sanitize(defaultValue)) , mValue(sanitize(Settings::Manager::getOrDefault(mName, mCategory, mDefaultValue))) { } SettingValue(SettingValue&& other) : BaseSettingValue(std::move(other)) , mSanitizer(std::move(other.mSanitizer)) , mDefaultValue(std::move(other.mValue)) , mValue(sanitize(Settings::Manager::get(mName, mCategory))) { } SettingValue(const SettingValue& other) = delete; SettingValue& operator=(const SettingValue& other) = delete; SettingValue& operator=(SettingValue&& other) = delete; 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); } void reset() { set(mDefaultValue); } private: std::unique_ptr> mSanitizer; T mDefaultValue{}; T mValue{}; struct WriteValue { const T& mValue; friend std::ostream& operator<<(std::ostream& stream, const WriteValue& value) { if constexpr (std::is_enum_v) return stream << static_cast>(value.mValue); else if constexpr (std::is_same_v>) { bool first = true; for (const std::string& v : value.mValue) { if (std::exchange(first, false)) stream << v; else stream << "," << v; } return stream; } else return stream << value.mValue; } }; T sanitize(const T& value) const { if (mSanitizer == nullptr) return value; try { T sanitizedValue = mSanitizer->apply(value); if (sanitizedValue != value) Log(Debug::Warning) << getSettingDescription(mCategory, mName) << " value is out of allowed values set: " << WriteValue{ value } << ", sanitized to " << WriteValue{ sanitizedValue }; return sanitizedValue; } catch (const std::exception& e) { throw std::runtime_error( "Invalid " + getSettingDescription(mCategory, mName) + " value: " + std::string(e.what())); } } }; template SettingValue* Index::find(std::string_view category, std::string_view name) const { const auto it = mValues.find({ category, name }); if (it == mValues.end()) return nullptr; BaseSettingValue* const value = it->second; if (value->mType != getSettingValueType()) throw std::invalid_argument(getSettingDescription(value->mType, category, name) + " does not match requested type: " + std::string(getSettingValueTypeName())); return static_cast*>(value); } template SettingValue& Index::get(std::string_view category, std::string_view name) const { SettingValue* const result = find(category, name); if (result == nullptr) throw std::invalid_argument(getSettingDescription(category, name) + " is not found"); return *result; } class WithIndex { public: explicit WithIndex(Index& index) : mIndex(index) { } protected: Index& mIndex; }; } #endif