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);