feat: add universal config manager implementation.
This commit is contained in:
parent
e1823d4b8e
commit
588946583c
|
@ -5,6 +5,7 @@ target_sources(YYCCommonplace
|
||||||
PRIVATE
|
PRIVATE
|
||||||
# Sources
|
# Sources
|
||||||
COMHelper.cpp
|
COMHelper.cpp
|
||||||
|
ConfigManager.cpp
|
||||||
ConsoleHelper.cpp
|
ConsoleHelper.cpp
|
||||||
DialogHelper.cpp
|
DialogHelper.cpp
|
||||||
EncodingHelper.cpp
|
EncodingHelper.cpp
|
||||||
|
@ -21,6 +22,7 @@ FILES
|
||||||
# Headers
|
# Headers
|
||||||
# Common headers
|
# Common headers
|
||||||
COMHelper.hpp
|
COMHelper.hpp
|
||||||
|
ConfigManager.hpp
|
||||||
ConsoleHelper.hpp
|
ConsoleHelper.hpp
|
||||||
DialogHelper.hpp
|
DialogHelper.hpp
|
||||||
EncodingHelper.hpp
|
EncodingHelper.hpp
|
||||||
|
|
148
src/ConfigManager.cpp
Normal file
148
src/ConfigManager.cpp
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
#include "ConfigManager.hpp"
|
||||||
|
|
||||||
|
#include "EncodingHelper.hpp"
|
||||||
|
#include "IOHelper.hpp"
|
||||||
|
|
||||||
|
namespace YYCC::ConfigManager {
|
||||||
|
|
||||||
|
#pragma region Core Manager
|
||||||
|
|
||||||
|
CoreManager::CoreManager(
|
||||||
|
const yycc_char8_t* cfg_file_path,
|
||||||
|
uint64_t version_identifier,
|
||||||
|
std::initializer_list<AbstractSetting*> settings) :
|
||||||
|
m_CfgFilePath(), m_VersionIdentifier(version_identifier), m_Settings() {
|
||||||
|
// assign cfg path
|
||||||
|
if (cfg_file_path != nullptr)
|
||||||
|
m_CfgFilePath = cfg_file_path;
|
||||||
|
// assign settings
|
||||||
|
for (auto* setting : settings) {
|
||||||
|
m_Settings.try_emplace(setting->GetName(), setting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CoreManager::Load() {
|
||||||
|
// reset all settings first
|
||||||
|
Reset();
|
||||||
|
|
||||||
|
// get file handle
|
||||||
|
auto fs = this->GetFileHandle(YYCC_U8("rb"));
|
||||||
|
if (fs.get() == nullptr) {
|
||||||
|
// if we fail to get, it means that we do not have corresponding cfg file.
|
||||||
|
// all settings should be reset to default value.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch version info
|
||||||
|
uint64_t version_info;
|
||||||
|
if (std::fread(&version_info, sizeof(version_info), 1u, fs.get()) != sizeof(version_info))
|
||||||
|
return false;
|
||||||
|
// check version
|
||||||
|
// if read version is greater than we expected,
|
||||||
|
// it means that this cfg file is created by the program higer than this.
|
||||||
|
// we should not read anything from it.
|
||||||
|
// however, for compaitibility reason, we allow read old cfg data.
|
||||||
|
if (version_info > m_VersionIdentifier)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// fetch setting item from file
|
||||||
|
yycc_u8string name_cache;
|
||||||
|
while (true) {
|
||||||
|
// try fetch setting name
|
||||||
|
// fetch name length
|
||||||
|
size_t name_length;
|
||||||
|
if (std::fread(&name_length, sizeof(name_length), 1u, fs.get()) != sizeof(name_length)) {
|
||||||
|
// we also check whether reach EOF at there.
|
||||||
|
if (std::feof(fs.get())) break;
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
// fetch name body
|
||||||
|
name_cache.resize(name_length);
|
||||||
|
if (std::fread(name_cache.data(), name_length, 1u, fs.get()) != name_length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// get setting data length
|
||||||
|
size_t data_length;
|
||||||
|
if (std::fread(&data_length, sizeof(data_length), 1u, fs.get()) != sizeof(data_length))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// get matched setting first
|
||||||
|
const auto& found = m_Settings.find(name_cache);
|
||||||
|
if (found != m_Settings.end()) {
|
||||||
|
// found. read data for it
|
||||||
|
found->second->ResizeData(data_length);
|
||||||
|
if (std::fread(found->second->GetDataPtr(), data_length, 1u, fs.get()) != data_length)
|
||||||
|
return false;
|
||||||
|
// call user defined load function
|
||||||
|
// if fail to parse, reset to default value
|
||||||
|
if (!found->second->UserLoad())
|
||||||
|
found->second->UserReset();
|
||||||
|
} else {
|
||||||
|
// fail to find. skip this unknown setting
|
||||||
|
if (fseek(fs.get(), static_cast<long>(data_length), SEEK_CUR) != 0)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CoreManager::Save() {
|
||||||
|
// get file handle
|
||||||
|
auto fs = this->GetFileHandle(YYCC_U8("wb"));
|
||||||
|
// if we fail to get, return false.
|
||||||
|
if (fs == nullptr) return false;
|
||||||
|
|
||||||
|
// write config data
|
||||||
|
uint64_t version_info = m_VersionIdentifier;
|
||||||
|
if (std::fwrite(&version_info, sizeof(version_info), 1u, fs.get()) != sizeof(version_info))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// iterate all data for writing
|
||||||
|
for (const auto& pair : m_Settings) {
|
||||||
|
// do user defined save
|
||||||
|
// if failed, skip this setting
|
||||||
|
if (!pair.second->UserSave())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// write setting name
|
||||||
|
// write name length
|
||||||
|
size_t name_length = pair.first.size();
|
||||||
|
if (std::fwrite(&name_length, sizeof(name_length), 1u, fs.get()) != sizeof(name_length))
|
||||||
|
return false;
|
||||||
|
// write name body
|
||||||
|
if (std::fwrite(pair.first.c_str(), name_length, 1u, fs.get()) != name_length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// write setting daat
|
||||||
|
// write data length
|
||||||
|
size_t data_length = pair.second->GetDataSize();
|
||||||
|
if (std::fwrite(&data_length, sizeof(data_length), 1u, fs.get()) != sizeof(data_length))
|
||||||
|
return false;
|
||||||
|
// write data body
|
||||||
|
if (std::fwrite(pair.second->GetDataPtr(), data_length, 1u, fs.get()) != data_length)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// all settings done, return true
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreManager::Reset() {
|
||||||
|
for (const auto& pair : m_Settings) {
|
||||||
|
pair.second->UserReset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreManager::FileHandleGuard_t CoreManager::GetFileHandle(const yycc_char8_t* mode) const {
|
||||||
|
return CoreManager::FileHandleGuard_t(
|
||||||
|
IOHelper::UTF8FOpen(this->m_CfgFilePath.c_str(), mode),
|
||||||
|
[](FILE* fs) -> void {
|
||||||
|
if (fs != nullptr) std::fclose(fs);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
}
|
200
src/ConfigManager.hpp
Normal file
200
src/ConfigManager.hpp
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
#pragma once
|
||||||
|
#include "YYCCInternal.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace YYCC::ConfigManager {
|
||||||
|
|
||||||
|
template<typename _Ty>
|
||||||
|
struct Constrain {
|
||||||
|
using CheckFct_t = std::function<bool(const _Ty&)>;
|
||||||
|
//using CorrectFct_t = std::function<_Ty(const _Ty&)>;
|
||||||
|
CheckFct_t m_CheckFct;
|
||||||
|
//CorrectFct_t m_CorrectFct;
|
||||||
|
|
||||||
|
bool IsValid() const {
|
||||||
|
return m_CheckFct != nullptr/* && m_CorrectFct != nullptr*/;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace ConstrainPresets {
|
||||||
|
|
||||||
|
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty>, int> = 0>
|
||||||
|
Constrain<_Ty> GetNumberRangeConstrain(_Ty min_value, _Ty max_value) {
|
||||||
|
if (min_value > max_value)
|
||||||
|
throw std::invalid_argument("invalid min max value for NumberRangeConstrain");
|
||||||
|
return Constrain<_Ty> {
|
||||||
|
[min_value, max_value](const _Ty& val) -> bool { return (val <= max_value) && (val >= min_value); }
|
||||||
|
/*[min_value, max_value](const _Ty& val) -> _Ty { return std::clamp(val, min_value, max_value); }*/
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class AbstractSetting {
|
||||||
|
friend class CoreManager;
|
||||||
|
public:
|
||||||
|
AbstractSetting(const yycc_char8_t* name) : m_Name(), m_RawData() {
|
||||||
|
if (name != nullptr) m_Name = name;
|
||||||
|
}
|
||||||
|
virtual ~AbstractSetting() {}
|
||||||
|
|
||||||
|
// Name interface
|
||||||
|
public:
|
||||||
|
const yycc_u8string& GetName() const { return m_Name; }
|
||||||
|
private:
|
||||||
|
yycc_u8string m_Name;
|
||||||
|
|
||||||
|
// User Implementations
|
||||||
|
protected:
|
||||||
|
virtual bool UserLoad() = 0;
|
||||||
|
virtual bool UserSave() = 0;
|
||||||
|
virtual void UserReset() = 0;
|
||||||
|
|
||||||
|
// Buffer related functions
|
||||||
|
protected:
|
||||||
|
void ResizeData(size_t new_size) { m_RawData.resize(new_size); }
|
||||||
|
const void* GetDataPtr() const { return m_RawData.data(); }
|
||||||
|
void* GetDataPtr() { return m_RawData.data(); }
|
||||||
|
size_t GetDataSize() const { return m_RawData.size(); }
|
||||||
|
private:
|
||||||
|
std::vector<uint8_t> m_RawData;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CoreManager {
|
||||||
|
public:
|
||||||
|
CoreManager(
|
||||||
|
const yycc_char8_t* cfg_file_path,
|
||||||
|
uint64_t version_identifier,
|
||||||
|
std::initializer_list<AbstractSetting*> settings);
|
||||||
|
~CoreManager() {}
|
||||||
|
|
||||||
|
// Core functions
|
||||||
|
public:
|
||||||
|
bool Load();
|
||||||
|
bool Save();
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
private:
|
||||||
|
using FileHandleGuard_t = std::unique_ptr<FILE, std::function<void(FILE*)>>;
|
||||||
|
FileHandleGuard_t GetFileHandle(const yycc_char8_t* mode) const;
|
||||||
|
|
||||||
|
yycc_u8string m_CfgFilePath;
|
||||||
|
uint64_t m_VersionIdentifier;
|
||||||
|
std::map<yycc_u8string, AbstractSetting*> m_Settings;
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma region Setting Presets
|
||||||
|
|
||||||
|
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty>, int> = 0>
|
||||||
|
class NumberSetting : public AbstractSetting {
|
||||||
|
public:
|
||||||
|
NumberSetting(const yycc_char8_t* name, _Ty default_value, Constrain<_Ty> constrain = Constrain<_Ty> {}) :
|
||||||
|
AbstractSetting(name), m_Data(default_value), m_DefaultData(default_value), m_Constrain(constrain) {}
|
||||||
|
virtual ~NumberSetting() {}
|
||||||
|
|
||||||
|
_Ty Get() const { return m_Data; }
|
||||||
|
bool Set(_Ty new_data) {
|
||||||
|
// validate data
|
||||||
|
if (m_Constrain.IsValid() && !m_Constrain.m_CheckFct(m_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<const _Ty*>(GetDataPtr());
|
||||||
|
// check data
|
||||||
|
if (m_Constrain.IsValid() && !m_Constrain.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;
|
||||||
|
Constrain<_Ty> m_Constrain;
|
||||||
|
};
|
||||||
|
|
||||||
|
class StringSetting : public AbstractSetting {
|
||||||
|
public:
|
||||||
|
StringSetting(const yycc_char8_t* name, const yycc_char8_t* default_value, Constrain<yycc_u8string> constrain = Constrain<yycc_u8string> {}) :
|
||||||
|
AbstractSetting(name), m_Data(), m_DefaultData(), m_Constrain(constrain) {
|
||||||
|
if (default_value != nullptr) {
|
||||||
|
m_Data = default_value;
|
||||||
|
m_DefaultData = default_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
virtual ~StringSetting() {}
|
||||||
|
|
||||||
|
const yycc_u8string& Get() const { return m_Data; }
|
||||||
|
bool Set(const yycc_char8_t* new_data) {
|
||||||
|
// check data validation
|
||||||
|
if (new_data == nullptr)
|
||||||
|
return false;
|
||||||
|
if (m_Constrain.IsValid() && !m_Constrain.m_CheckFct(m_Data))
|
||||||
|
return false;
|
||||||
|
// assign data
|
||||||
|
m_Data = new_data;
|
||||||
|
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<const size_t*>(GetDataPtr());
|
||||||
|
// read string body
|
||||||
|
if (GetDataSize() != sizeof(string_length) + string_length)
|
||||||
|
return false;
|
||||||
|
m_Data.assign(
|
||||||
|
reinterpret_cast<const yycc_char8_t*>(static_cast<const uint8_t*>(GetDataPtr()) + sizeof(string_length)),
|
||||||
|
string_length
|
||||||
|
);
|
||||||
|
// check data
|
||||||
|
if (m_Constrain.IsValid() && !m_Constrain.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<uint8_t*>(GetDataPtr());
|
||||||
|
// assign string length
|
||||||
|
*reinterpret_cast<size_t*>(ptr) = string_length;
|
||||||
|
// assign string body
|
||||||
|
std::memcpy(ptr + sizeof(string_length), m_Data.data(), string_length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
yycc_u8string m_Data, m_DefaultData;
|
||||||
|
Constrain<yycc_u8string> m_Constrain;
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -12,3 +12,5 @@
|
||||||
#include "WinFctHelper.hpp"
|
#include "WinFctHelper.hpp"
|
||||||
#include "FsPathPatch.hpp"
|
#include "FsPathPatch.hpp"
|
||||||
#include "ExceptionHelper.hpp"
|
#include "ExceptionHelper.hpp"
|
||||||
|
|
||||||
|
#include "ConfigManager.hpp"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user