diff --git a/doc/src/enum_helper.dox b/doc/src/enum_helper.dox
new file mode 100644
index 0000000..82ea6a9
--- /dev/null
+++ b/doc/src/enum_helper.dox
@@ -0,0 +1,35 @@
+namespace YYCC::EnumHelper {
+/**
+
+\page enum_helper Scoped Enum Helper
+
+\section enum_helper__intro Intro
+
+C++ introduce a new enum called scoped enum.
+It is better than legacy C enum because it will not leak name into namespace where it locate,
+and also can specify an underlying type to it to make sure it is stored as specified size.
+However, the shortcoming of it is that it lack bitwise operator comparing with legacy C enum.
+Programmer must implement them for scoped enum one by one.
+It is a hardship and inconvenient.
+This is the reason why I invent this class
+
+\section enum_helper__Usage Usage
+
+In this namespace, we provide all bitwise functions related to scoped enum type which may be used.
+See YYCC::EnumHelper for more detail (It is more clear to read function annotation than I introduce in there repeatedly).
+
+\section enum_helper__why Why not Operator Overload
+
+I have try it (and you even can see the relic of it in source code).
+But it need a extra statement written in following to include it, otherwise compiler can not see it.
+
+\code
+using namespace YYCC::EnumHelper;
+\endcode
+
+Another reason why I do not use this method is that
+this overload strategy may be applied to some type which should not be applied by accient, such as non-scoped enum type.
+So I gave up this solution.
+
+*/
+}
\ No newline at end of file
diff --git a/doc/src/index.dox b/doc/src/index.dox
index 619ca97..2449c97 100644
--- a/doc/src/index.dox
+++ b/doc/src/index.dox
@@ -45,6 +45,8 @@
\li \subpage std_patch
+ \li \subpage enum_helper
+
Advanced Features
\li \subpage constraints
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 29b9dc6..11097eb 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -31,6 +31,7 @@ FILES
ConsoleHelper.hpp
DialogHelper.hpp
EncodingHelper.hpp
+ EnumHelper.hpp
ExceptionHelper.hpp
StdPatch.hpp
IOHelper.hpp
diff --git a/src/ConfigManager.cpp b/src/ConfigManager.cpp
index 407285c..4f55c0a 100644
--- a/src/ConfigManager.cpp
+++ b/src/ConfigManager.cpp
@@ -2,6 +2,7 @@
#include "EncodingHelper.hpp"
#include "IOHelper.hpp"
+#include "EnumHelper.hpp"
#include
namespace YYCC::ConfigManager {
@@ -33,7 +34,7 @@ namespace YYCC::ConfigManager {
m_CfgFilePath(cfg_file_path), m_VersionIdentifier(version_identifier), m_Settings() {
// Mark: no need to check cfg file path
// it will be checked at creating file handle
-
+
// assign settings
for (auto* setting : settings) {
auto result = m_Settings.try_emplace(setting->GetName(), setting);
@@ -44,7 +45,10 @@ namespace YYCC::ConfigManager {
}
}
- bool CoreManager::Load() {
+ ConfigLoadResult CoreManager::Load() {
+ // prepare result variables
+ ConfigLoadResult ret = ConfigLoadResult::OK;
+
// reset all settings first
Reset();
@@ -53,20 +57,27 @@ namespace YYCC::ConfigManager {
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;
+ ret = ConfigLoadResult::Created;
+ return ret;
}
// fetch version info
uint64_t version_info;
- if (std::fread(&version_info, 1u, sizeof(version_info), fs.get()) != sizeof(version_info))
- return false;
+ if (std::fread(&version_info, 1u, sizeof(version_info), fs.get()) != sizeof(version_info)) {
+ ret = ConfigLoadResult::Created;
+ return ret;
+ }
// 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;
+ if (version_info > m_VersionIdentifier) {
+ ret = ConfigLoadResult::ForwardNew;
+ return ret;
+ } else if (version_info < m_VersionIdentifier) {
+ EnumHelper::Add(ret, ConfigLoadResult::Migrated);
+ }
// fetch setting item from file
yycc_u8string name_cache;
@@ -77,37 +88,50 @@ namespace YYCC::ConfigManager {
if (std::fread(&name_length, 1u, sizeof(name_length), fs.get()) != sizeof(name_length)) {
// we also check whether reach EOF at there.
if (std::feof(fs.get())) break;
- else return false;
+ else {
+ EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
+ return ret;
+ }
}
// fetch name body
name_cache.resize(name_length);
- if (std::fread(name_cache.data(), 1u, name_length, fs.get()) != name_length)
- return false;
+ if (std::fread(name_cache.data(), 1u, name_length, fs.get()) != name_length) {
+ EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
+ return ret;
+ }
// get setting data length
size_t data_length;
- if (std::fread(&data_length, 1u, sizeof(data_length), fs.get()) != sizeof(data_length))
- return false;
+ if (std::fread(&data_length, 1u, sizeof(data_length), fs.get()) != sizeof(data_length)) {
+ EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
+ return ret;
+ }
// 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(), 1u, data_length, fs.get()) != data_length)
- return false;
+ if (std::fread(found->second->GetDataPtr(), 1u, data_length, fs.get()) != data_length) {
+ EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
+ return ret;
+ }
// call user defined load function
// if fail to parse, reset to default value
- if (!found->second->UserLoad())
+ if (!found->second->UserLoad()) {
+ EnumHelper::Add(ret, ConfigLoadResult::ItemError);
found->second->UserReset();
+ }
} else {
// fail to find. skip this unknown setting
- if (fseek(fs.get(), static_cast(data_length), SEEK_CUR) != 0)
- return false;
+ if (fseek(fs.get(), static_cast(data_length), SEEK_CUR) != 0) {
+ EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
+ return ret;
+ }
}
}
- return true;
+ return ret;
}
bool CoreManager::Save() {
diff --git a/src/ConfigManager.hpp b/src/ConfigManager.hpp
index 0f453d2..b3c6f2b 100644
--- a/src/ConfigManager.hpp
+++ b/src/ConfigManager.hpp
@@ -17,6 +17,19 @@
* @details For how to use this namespace, please see \ref config_manager.
*/
namespace YYCC::ConfigManager {
+
+ /**
+ * @brief The load result of loading config.
+ */
+ enum class ConfigLoadResult {
+ OK = 0, ///< Success load configs.
+ Created = 1 << 0, ///< Given file is not existing, we create all configs in default values.
+ ForwardNew = 1 << 1, ///< Detect the config file created by higher version. We create all configs in default values.
+ Migrated = 1 << 2, ///< Detect the config file created by lower version. We try migrate configs written in it.
+ BrokenFile = 1 << 3, ///< Given file has bad format. Thus some configs are kept as its default values.
+ ItemError = 1 << 4 ///< Some config can not be recognized from the data read from file so they are reset to default value.
+ };
+ using UnderlyingConfigLoadResult_t = std::underlying_type_t;
/// @brief The base class of every setting.
/// @details Programmer can inherit this class and implement essential functions to create custom setting.
@@ -75,7 +88,7 @@ namespace YYCC::ConfigManager {
private:
std::vector m_RawData;
};
-
+
/// @brief Settings manager and config file reader writer.
class CoreManager {
public:
@@ -96,8 +109,8 @@ namespace YYCC::ConfigManager {
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();
+ /// @return What happend when loading config. This function always success.
+ ConfigLoadResult Load();
/// @brief Save settings to file.
/// @return True if success, otherwise false.
bool Save();
diff --git a/src/EnumHelper.hpp b/src/EnumHelper.hpp
new file mode 100644
index 0000000..440dddc
--- /dev/null
+++ b/src/EnumHelper.hpp
@@ -0,0 +1,177 @@
+#pragma once
+#include "YYCCInternal.hpp"
+
+#include
+#include
+
+/**
+ * @brief The namespace for convenient C++ enum class logic operations.
+ * @details
+ * C++ enum class statement is a modern way to declare enum in C++.
+ * But it lack essential logic operations which is commonly used by programmer.
+ * So we create this helper to resolve this issue.
+*/
+namespace YYCC::EnumHelper {
+
+ //// Reference:
+ //// Enum operator overload: https://stackoverflow.com/a/71107019
+ //// Constexpr operator overload: https://stackoverflow.com/a/17746099
+
+ //template, int> = 0>
+ //inline constexpr TEnum operator|(TEnum lhs, TEnum rhs) {
+ // using ut = std::underlying_type_t;
+ // return static_cast(static_cast(lhs) | static_cast(rhs));
+ //}
+ //template, int> = 0>
+ //inline constexpr TEnum operator|=(TEnum& lhs, TEnum rhs) {
+ // using ut = std::underlying_type_t;
+ // lhs = lhs | rhs;
+ // return lhs;
+ //}
+ //
+ //template, int> = 0>
+ //inline constexpr TEnum operator&(TEnum lhs, TEnum rhs) {
+ // using ut = std::underlying_type_t;
+ // return static_cast(static_cast(lhs) & static_cast(rhs));
+ //}
+ //template, int> = 0>
+ //inline constexpr TEnum operator&=(TEnum& lhs, TEnum rhs) {
+ // using ut = std::underlying_type_t;
+ // lhs = lhs & rhs;
+ // return lhs;
+ //}
+ //
+ //template, int> = 0>
+ //inline constexpr TEnum operator^(TEnum lhs, TEnum rhs) {
+ // using ut = std::underlying_type_t;
+ // return static_cast(static_cast(lhs) ^ static_cast(rhs));
+ //}
+ //template, int> = 0>
+ //inline constexpr TEnum operator^=(TEnum& lhs, TEnum rhs) {
+ // using ut = std::underlying_type_t;
+ // lhs = lhs ^ rhs;
+ // return lhs;
+ //}
+
+ //template, int> = 0>
+ //inline constexpr TEnum operator~(TEnum lhs) {
+ // using ut = std::underlying_type_t;
+ // return static_cast(~(static_cast(lhs)));
+ //}
+ //
+ //template, int> = 0>
+ //inline constexpr bool operator bool(TEnum lhs) {
+ // using ut = std::underlying_type_t;
+ // return static_cast(static_cast(lhs));
+ //}
+
+ /**
+ * @brief The helper struct to check all given template argument are the same enum type.
+ * @tparam TEnum The template parameter to be checked (first one).
+ * @tparam Ts The template parameter to be checked.
+ */
+ template
+ struct all_enum_values {
+ public:
+ // Please note it is std::is_same, not std::is_same_v!
+ // That's std::conjunction_v required.
+ static constexpr bool value = std::is_enum_v> && std::conjunction_v, std::remove_cv_t>...>;
+ };
+ /**
+ * @brief The convenient calling to all_enum_values::value to check enum template parameter.
+ * @tparam TEnum The template parameter to be checked (first one).
+ * @tparam Ts The template parameter to be checked.
+ */
+ template
+ inline constexpr bool all_enum_values_v = all_enum_values::value;
+
+ /**
+ * @brief Merge given enum flags like performing e1 | e2 | ... | en
+ * @tparam TEnum Enum type for processing.
+ * @param[in] il The list of enum flags to be merged.
+ * @return The merged enum flag.
+ */
+ template, int> = 0>
+ constexpr TEnum Merge(TEnum val, Ts... val_left) {
+ using ut = std::underlying_type_t;
+ ut result = static_cast(val);
+ if constexpr (sizeof...(val_left) > 0) {
+ result |= static_cast(Merge(val_left...));
+ }
+ return static_cast(result);
+ }
+
+ /**
+ * @brief Reverse given enum flags like performing ~(e)
+ * @tparam TEnum Enum type for processing.
+ * @param[in] e The list of enum flags to be inversed.
+ * @return The inversed enum flag.
+ */
+ template, int> = 0>
+ constexpr TEnum Invert(TEnum e) {
+ using ut = std::underlying_type_t;
+ return static_cast(~(static_cast(e)));
+ }
+
+ /**
+ * @brief Use specified enum flags to mask given enum flags like performing e1 &= e2
+ * @tparam TEnum Enum type for processing.
+ * @param[in,out] e1 The enum flags to be masked.
+ * @param[in] e2 The mask enum flag.
+ */
+ template, int> = 0>
+ constexpr void Mask(TEnum& e1, TEnum e2) {
+ using ut = std::underlying_type_t;
+ e1 = static_cast(static_cast(e1) & static_cast(e2));
+ }
+
+ /**
+ * @brief Add specified enum flags to given enum flags like performing e1 = e1 | e2 | ... | en
+ * @tparam TEnum Enum type for processing.
+ * @param[in,out] e1 The enum flags to be processed.
+ * @param[in] e2 The enum flag to be added.
+ */
+ template, int> = 0>
+ constexpr void Add(TEnum& e1, Ts... vals) {
+ using ut = std::underlying_type_t;
+ e1 = static_cast(static_cast(e1) | static_cast(Merge(vals...)));
+ }
+
+ /**
+ * @brief Remove specified enum flags from given enum flags like performing e1 &= ~(e2 | e3 | ... | en)
+ * @tparam TEnum Enum type for processing.
+ * @param[in,out] e1 The enum flags to be processed.
+ * @param[in] e2 The enum flag to be removed.
+ */
+ template, int> = 0>
+ constexpr void Remove(TEnum& e1, Ts... vals) {
+ using ut = std::underlying_type_t;
+ e1 = static_cast(static_cast(e1) & static_cast(Invert(Merge(vals...))));
+ }
+
+ /**
+ * @brief Check whether given enum flags has specified enum flag like performing bool(e & probe)
+ * @tparam TEnum Enum type for processing.
+ * @param[in] e1 The enum flags to be checked.
+ * @param[in] e2 The enum flag for checking.
+ * @return True if it has, otherwise false.
+ */
+ template, int> = 0>
+ constexpr bool Has(TEnum e1, TEnum e2) {
+ using ut = std::underlying_type_t;
+ return static_cast(static_cast(e1) & static_cast(e2));
+ }
+
+ /**
+ * @brief Cast given enum flags to its equvalent boolean value like performing bool(e)
+ * @tparam TEnum Enum type for processing.
+ * @param e The enum flags to be cast.
+ * @return The cast enum flag.
+ */
+ template, int> = 0>
+ constexpr bool Bool(TEnum e) {
+ using ut = std::underlying_type_t;
+ return static_cast(static_cast(e));
+ }
+
+}
diff --git a/src/YYCCommonplace.hpp b/src/YYCCommonplace.hpp
index 7ccc730..05b355a 100644
--- a/src/YYCCommonplace.hpp
+++ b/src/YYCCommonplace.hpp
@@ -11,6 +11,7 @@
#include "IOHelper.hpp"
#include "WinFctHelper.hpp"
#include "StdPatch.hpp"
+#include "EnumHelper.hpp"
#include "ExceptionHelper.hpp"
#include "ConfigManager.hpp"
diff --git a/testbench/main.cpp b/testbench/main.cpp
index 0c8c562..791473d 100644
--- a/testbench/main.cpp
+++ b/testbench/main.cpp
@@ -402,7 +402,7 @@ namespace YYCCTestbench {
#endif
}
- static void StdPatch() {
+ static void StdPatchTestbench() {
// Std Path
@@ -439,6 +439,51 @@ namespace YYCCTestbench {
}
+ enum class TestFlagEnum : uint8_t {
+ Test1 = 0b00000000,
+ Test2 = 0b00000001,
+ Test3 = 0b00000010,
+ Test4 = 0b00000100,
+ Test5 = 0b00001000,
+ Test6 = 0b00010000,
+ Test7 = 0b00100000,
+ Test8 = 0b01000000,
+ Test9 = 0b10000000,
+ Inverted = 0b01111111,
+ Merged = Test3 + Test5,
+ };
+
+ static void EnumHelperTestbench() {
+ TestFlagEnum val;
+
+ Assert(YYCC::EnumHelper::Merge(TestFlagEnum::Test3, TestFlagEnum::Test5) == TestFlagEnum::Merged, YYCC_U8("YYCC::EnumHelper::Merge"));
+
+ Assert(YYCC::EnumHelper::Invert(TestFlagEnum::Test9) == TestFlagEnum::Inverted, YYCC_U8("YYCC::EnumHelper::Invert"));
+
+ val = YYCC::EnumHelper::Merge(TestFlagEnum::Test3, TestFlagEnum::Test5);
+ YYCC::EnumHelper::Mask(val, TestFlagEnum::Test3);
+ Assert(YYCC::EnumHelper::Bool(val), YYCC_U8("YYCC::EnumHelper::Mask"));
+ val = YYCC::EnumHelper::Merge(TestFlagEnum::Test3, TestFlagEnum::Test5);
+ YYCC::EnumHelper::Mask(val, TestFlagEnum::Test4);
+ Assert(!YYCC::EnumHelper::Bool(val), YYCC_U8("YYCC::EnumHelper::Mask"));
+
+ val = TestFlagEnum::Test3;
+ YYCC::EnumHelper::Add(val, TestFlagEnum::Test5);
+ Assert(val == TestFlagEnum::Merged, YYCC_U8("YYCC::EnumHelper::Add"));
+
+ val = TestFlagEnum::Merged;
+ YYCC::EnumHelper::Remove(val, TestFlagEnum::Test5);
+ Assert(val == TestFlagEnum::Test3, YYCC_U8("YYCC::EnumHelper::Remove"));
+
+ val = YYCC::EnumHelper::Merge(TestFlagEnum::Test3, TestFlagEnum::Test5);
+ Assert(YYCC::EnumHelper::Has(val, TestFlagEnum::Test3), YYCC_U8("YYCC::EnumHelper::Has"));
+ Assert(!YYCC::EnumHelper::Has(val, TestFlagEnum::Test4), YYCC_U8("YYCC::EnumHelper::Has"));
+
+ Assert(!YYCC::EnumHelper::Bool(TestFlagEnum::Test1), YYCC_U8("YYCC::EnumHelper::Bool"));
+ Assert(YYCC::EnumHelper::Bool(TestFlagEnum::Test2), YYCC_U8("YYCC::EnumHelper::Bool"));
+
+ }
+
enum class TestEnum : int8_t {
Test1, Test2, Test3
};
@@ -518,7 +563,11 @@ namespace YYCCTestbench {
Assert(test.m_EnumSetting.Get() == TestEnum::Test1, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
// test load
- Assert(test.m_CoreManager.Load(), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
+ YYCC::ConfigManager::ConfigLoadResult wrong_result = YYCC::EnumHelper::Merge(
+ YYCC::ConfigManager::ConfigLoadResult::ItemError,
+ YYCC::ConfigManager::ConfigLoadResult::BrokenFile
+ );
+ Assert(!YYCC::EnumHelper::Has(test.m_CoreManager.Load(), wrong_result), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
test.PrintSettings();
Assert(test.m_IntSetting.Get() == INT32_C(114), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
Assert(test.m_FloatSetting.Get() == 2.0f, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
@@ -655,7 +704,8 @@ int main(int argc, char* argv[]) {
YYCCTestbench::StringTestbench();
YYCCTestbench::ParserTestbench();
YYCCTestbench::WinFctTestbench();
- YYCCTestbench::StdPatch();
+ YYCCTestbench::StdPatchTestbench();
+ YYCCTestbench::EnumHelperTestbench();
// advanced
YYCCTestbench::ConfigManagerTestbench();
YYCCTestbench::ArgParserTestbench(argc, argv);