#pragma once #include "YYCCInternal.hpp" #include "Constraints.hpp" #include #include #include #include #include #include #include #include #include /** * @brief Universal configuration manager * @details For how to use this namespace, please see \ref config_manager. */ namespace YYCC::ConfigManager { /// @brief The base class of every setting. /// @details Programmer can inherit this class and implement essential functions to create custom setting. class AbstractSetting { friend class CoreManager; public: /** * @brief Construct a setting * @param[in] name The name of this setting. */ AbstractSetting(const yycc_char8_t* name) : m_Name(), m_RawData() { if (name != nullptr) m_Name = name; } virtual ~AbstractSetting() {} // Name interface public: /// @brief Get name of this setting. /// @details Name was used in storing setting in file. const yycc_u8string& GetName() const { return m_Name; } private: yycc_u8string m_Name; // User Implementations protected: /// @brief User implemented custom load function /// @remarks /// In this function, programmer should read data from internal buffer /// and store it to its own another internal variables. /// @return True if success, otherwise false. virtual bool UserLoad() = 0; /// @brief User implemented custom save function /// @remarks /// In this function, programmer should write data, /// which is stored in another variavle by it own, to internal buffer. /// @return True if success, otherwise false. virtual bool UserSave() = 0; /// @brief User implemented custom reset function /// @remarks In this function, programmer should reset its internal variable to default value. virtual void UserReset() = 0; // Buffer related functions protected: /// @brief Resize internal buffer to given size. /// @remarks It is usually used in UserSave. /// @param[in] new_size The new size of internal buffer. void ResizeData(size_t new_size) { m_RawData.resize(new_size); } /// @brief Get data pointer to internal buffer. /// @remarks It is usually used in UserLoad. const void* GetDataPtr() const { return m_RawData.data(); } /// @brief Get mutable data pointer to internal buffer. /// @remarks It is usually used in UserSave. void* GetDataPtr() { return m_RawData.data(); } /// @brief Get the length of internal buffer. size_t GetDataSize() const { return m_RawData.size(); } private: std::vector m_RawData; }; /// @brief Settings manager and config file reader writer. class CoreManager { public: /** * @brief Build core manager. * @param[in] cfg_file_path The path to config file. * @param[in] version_identifier The identifier of version. Higher is newer. Lower config will try doing migration. * @param[in] settings An initializer list containing pointers to all managed settings. */ CoreManager( const yycc_char8_t* cfg_file_path, uint64_t version_identifier, std::initializer_list settings); ~CoreManager() {} // Core functions public: /// @brief Load settings from file. /// @details Before loading, all settings will be reset to default value first. /// @return True if success, otherwise false. bool Load(); /// @brief Save settings to file. /// @return True if success, otherwise false. bool Save(); /// @brief Reset all settings to default value. void Reset(); private: using FileHandleGuard_t = std::unique_ptr>; FileHandleGuard_t GetFileHandle(const yycc_char8_t* mode) const; yycc_u8string m_CfgFilePath; uint64_t m_VersionIdentifier; std::map m_Settings; }; #pragma region Setting Presets /** * @brief Arithmetic (integral, floating point, bool) and enum type setting * @tparam _Ty The internal stored type belongs to arithmetic type. */ template || std::is_enum_v<_Ty>, int> = 0> class NumberSetting : public AbstractSetting { public: /** * @brief Construct arithmetic type setting. * @param[in] name The name of this setting. * @param[in] default_value The default value of this setting. * @param[in] constraint The constraint applied to this setting. */ NumberSetting( const yycc_char8_t* name, _Ty default_value, Constraints::Constraint<_Ty> constraint = Constraints::Constraint<_Ty> {}) : AbstractSetting(name), m_Data(default_value), m_DefaultData(default_value), m_Constraint(constraint) {} virtual ~NumberSetting() {} /// @brief Get stored data in setting. _Ty Get() const { return m_Data; } /** * @brief Set data to setting. * @param[in] new_data The new data. * @return True if success, otherwise false (given value is invalid) */ bool Set(_Ty new_data) { // validate data if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(new_data)) return false; // assign data m_Data = new_data; return true; } protected: virtual bool UserLoad() override { // read data if (sizeof(m_Data) != GetDataSize()) return false; m_Data = *reinterpret_cast(GetDataPtr()); // check data if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data)) return false; return true; } virtual bool UserSave() override { // write data ResizeData(sizeof(m_Data)); *reinterpret_cast<_Ty*>(GetDataPtr()) = m_Data; return true; } virtual void UserReset() override { m_Data = m_DefaultData; } _Ty m_Data, m_DefaultData; Constraints::Constraint<_Ty> m_Constraint; }; /// @brief String type setting class StringSetting : public AbstractSetting { public: /** * @brief Construct string setting * @param[in] name The name of this setting. * @param[in] default_value The default value of this setting. * @param[in] constraint The constraint applied to this setting. */ StringSetting( const yycc_char8_t* name, const yycc_u8string_view& default_value, Constraints::Constraint constraint = Constraints::Constraint {}) : AbstractSetting(name), m_Data(), m_DefaultData(), m_Constraint(constraint) { m_Data = default_value; m_DefaultData = default_value; } virtual ~StringSetting() {} /// @brief Get reference to stored string. const yycc_u8string& Get() const { return m_Data; } /** * @brief Set string data to setting. * @param[in] new_data The new string data. * @return True if success, otherwise false (given value is invalid) */ bool Set(const yycc_u8string_view& new_data) { // check data validation yycc_u8string new_data_cache(new_data); if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(new_data_cache)) return false; // assign data m_Data = std::move(new_data_cache); return true; } protected: virtual bool UserLoad() override { // read string length size_t string_length; if (GetDataSize() < sizeof(string_length)) return false; string_length = *reinterpret_cast(GetDataPtr()); // read string body if (GetDataSize() != sizeof(string_length) + string_length) return false; m_Data.assign( reinterpret_cast(static_cast(GetDataPtr()) + sizeof(string_length)), string_length ); // check data if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data)) return false; return true; } virtual bool UserSave() override { // allocate result buffer size_t string_length = m_Data.size(); ResizeData(sizeof(string_length) + string_length); // get pointer uint8_t* ptr = static_cast(GetDataPtr()); // assign string length *reinterpret_cast(ptr) = string_length; // assign string body std::memcpy(ptr + sizeof(string_length), m_Data.data(), string_length); return true; } virtual void UserReset() override { m_Data = m_DefaultData; } yycc_u8string m_Data, m_DefaultData; Constraints::Constraint m_Constraint; }; #pragma endregion }