diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 034c7e8..9628e45 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -4,23 +4,34 @@ project(core VERSION 0.2.1 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 23) set(KLOTSKI_CORE_SRC - all_cases/basic_ranges.cc - all_cases/all_cases.cc + all_cases/internal/basic_ranges.cc + all_cases/internal/all_cases.cc + common_code/common_code.cc common_code/serialize.cc common_code/sundry.cc + raw_code/raw_code.cc raw_code/convert.cc raw_code/sundry.cc raw_code/mirror.cc + short_code/convert.cc short_code/serialize.cc short_code/short_code.cc short_code/sundry.cc ) + add_library(klotski_core STATIC ${KLOTSKI_CORE_SRC}) target_compile_options(klotski_core PRIVATE -fno-rtti -fno-exceptions) -target_include_directories(klotski_core PUBLIC utils all_cases raw_code short_code common_code) +target_include_directories(klotski_core PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/utils # TODO: remove item + ${CMAKE_CURRENT_SOURCE_DIR}/all_cases # TODO: remove item + ${CMAKE_CURRENT_SOURCE_DIR}/raw_code # TODO: remove item + ${CMAKE_CURRENT_SOURCE_DIR}/short_code # TODO: remove item + ${CMAKE_CURRENT_SOURCE_DIR}/common_code # TODO: remove item +) # TODO: just for dev testing add_executable(klotski_core_bin main.cc) diff --git a/src/core/all_cases/all_cases.h b/src/core/all_cases/all_cases.h index 1503912..3591dad 100644 --- a/src/core/all_cases/all_cases.h +++ b/src/core/all_cases/all_cases.h @@ -1,4 +1,4 @@ -#pragma once +/// Klotski Engine by Dnomd343 @2024 /// Based on the requirements of valid klotski, the 2x2 block that must exist /// and only one, witch will occupy 4 empty slots, and the remaining 16 slots @@ -35,6 +35,8 @@ /// not consume too much time, but it can almost double the speed of the case /// checking subsequent. +#pragma once + #include #include #include @@ -42,11 +44,11 @@ #include #include -#include "utility.h" +#include "utils/utility.h" namespace klotski::cases { -// ----------------------------------------------------------------------------------------- // +// ------------------------------------------------------------------------------------- // typedef uint32_t Range; typedef std::vector Ranges; @@ -55,7 +57,7 @@ typedef std::array RangesUnion; typedef std::function Notifier; typedef std::function&&)> Executor; -// ----------------------------------------------------------------------------------------- // +// ------------------------------------------------------------------------------------- // constexpr auto BASIC_RANGES_NUM = 7311921; @@ -66,56 +68,75 @@ constexpr std::array ALL_CASES_NUM { 2942906, 2260392, 2942906, 0, }; +// TODO: move to short_code namespace (also `numeric` header) constexpr auto ALL_CASES_NUM_ = std::accumulate( - ALL_CASES_NUM.begin(), ALL_CASES_NUM.end(), 0 -); + ALL_CASES_NUM.begin(), ALL_CASES_NUM.end(), 0); -// ----------------------------------------------------------------------------------------- // +// ------------------------------------------------------------------------------------- // class BasicRanges { public: - void build() noexcept; - const Ranges& fetch() noexcept; - [[nodiscard]] bool is_available() const noexcept; + /// Execute the build process and ensure thread safety. + void build(); - MARK_INSTANCE(BasicRanges); - static BasicRanges& instance() noexcept; + /// Get the basic-ranges and make sure the result is available. + const Ranges& fetch(); + + /// Determine whether the basic-ranges data is available. + [[nodiscard]] bool is_available() const; private: - std::mutex building_; bool available_ = false; + std::mutex building_ {}; + + /// Get static singleton variable. + static Ranges& get_ranges(); + + /// Search and sort all possible basic-ranges permutations. + static void build_ranges(Ranges &ranges); - BasicRanges() = default; - static Ranges& get_ranges() noexcept; - static void build_ranges(Ranges &ranges) noexcept; - static void spawn_ranges(Ranges &ranges, int, int, int, int) noexcept; + /// Spawn all range permutations of specified conditions. + static void spawn_ranges(Ranges &ranges, int, int, int, int); + + KLSK_INSTANCE(BasicRanges) }; -// ----------------------------------------------------------------------------------------- // +// ------------------------------------------------------------------------------------- // class AllCases { public: - void build() noexcept; - void build_parallel(Executor &&executor) noexcept; - void build_parallel_async(Executor &&executor, Notifier &&callback) noexcept; + /// Execute the build process and ensure thread safety. + void build(); + + /// TODO: remove this interface + /// Execute the build process with parallel support and ensure thread safety. + void build_parallel(Executor &&executor); + + /// Execute the build process in parallel without blocking. + void build_parallel_async(Executor &&executor, Notifier &&callback); - const RangesUnion& fetch() noexcept; - [[nodiscard]] bool is_available() const noexcept; + /// Get all-cases and make sure the result is available. + const RangesUnion& fetch(); - MARK_INSTANCE(AllCases); - static AllCases& instance() noexcept; + /// Determine whether the all-cases data is available. + [[nodiscard]] bool is_available() const; private: - std::mutex building_; bool available_ = false; + std::mutex building_ {}; + + /// Get static singleton variable. + static RangesUnion& get_cases(); + + /// Build all valid ranges of the specified head. + static void build_cases(int head, Ranges &release); - AllCases() = default; - static RangesUnion& get_cases() noexcept; - static void build_cases(int head, Ranges &release) noexcept; + KLSK_INSTANCE(AllCases) }; -// ----------------------------------------------------------------------------------------- // +// ------------------------------------------------------------------------------------- // } // namespace klotski::cases -#include "inline_impl.inl" +#include "internal/basic_ranges.inl" +#include "internal/all_cases.inl" diff --git a/src/core/all_cases/inline_impl.inl b/src/core/all_cases/inline_impl.inl deleted file mode 100644 index 151fbc6..0000000 --- a/src/core/all_cases/inline_impl.inl +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -namespace klotski::cases { - -// ----------------------------------------------------------------------------------------- // - -inline BasicRanges& BasicRanges::instance() noexcept { - static BasicRanges instance; - return instance; -} - -inline Ranges& BasicRanges::get_ranges() noexcept { - static Ranges ranges; - return ranges; -} - -inline const Ranges& BasicRanges::fetch() noexcept { - this->build(); - return get_ranges(); -} - -inline bool BasicRanges::is_available() const noexcept { - return available_; // no mutex required in one-way state -} - -// ----------------------------------------------------------------------------------------- // - -inline AllCases& AllCases::instance() noexcept { - static AllCases instance; - return instance; -} - -inline RangesUnion& AllCases::get_cases() noexcept { - static RangesUnion cases; - return cases; -} - -inline const RangesUnion& AllCases::fetch() noexcept { - this->build(); - return get_cases(); -} - -inline bool AllCases::is_available() const noexcept { - return available_; // no mutex required in one-way state -} - -// ----------------------------------------------------------------------------------------- // - -} // namespace klotski::codec diff --git a/src/core/all_cases/all_cases.cc b/src/core/all_cases/internal/all_cases.cc similarity index 89% rename from src/core/all_cases/all_cases.cc rename to src/core/all_cases/internal/all_cases.cc index 9af44f4..1201532 100644 --- a/src/core/all_cases/all_cases.cc +++ b/src/core/all_cases/internal/all_cases.cc @@ -1,6 +1,6 @@ #include -#include "all_cases.h" +#include "all_cases/all_cases.h" namespace klotski::cases { @@ -49,8 +49,7 @@ static int check_range(const int head, uint32_t range) noexcept { return 0; // pass check } -/// Build all valid ranges of the specified head. -void AllCases::build_cases(const int head, Ranges &release) noexcept { +void AllCases::build_cases(const int head, Ranges &release) { release.clear(); release.reserve(ALL_CASES_NUM[head]); auto &basic_ranges = BasicRanges::instance().fetch(); @@ -70,15 +69,13 @@ void AllCases::build_cases(const int head, Ranges &release) noexcept { } } -/// Execute the build process and ensure thread safety. -void AllCases::build() noexcept { +void AllCases::build() { build_parallel([](auto &&func) { func(); }); } -/// Execute the build process with parallel support and ensure thread safety. -void AllCases::build_parallel(Executor &&executor) noexcept { +void AllCases::build_parallel(Executor &&executor) { if (available_) { return; // reduce consumption of mutex } @@ -101,8 +98,7 @@ void AllCases::build_parallel(Executor &&executor) noexcept { available_ = true; } -/// Execute the build process in parallel without blocking. -void AllCases::build_parallel_async(Executor &&executor, Notifier &&callback) noexcept { +void AllCases::build_parallel_async(Executor &&executor, Notifier &&callback) { if (available_) { callback(); return; // reduce consumption of mutex diff --git a/src/core/all_cases/internal/all_cases.inl b/src/core/all_cases/internal/all_cases.inl new file mode 100644 index 0000000..17132e9 --- /dev/null +++ b/src/core/all_cases/internal/all_cases.inl @@ -0,0 +1,21 @@ +#pragma once + +namespace klotski::cases { + +inline RangesUnion& AllCases::get_cases() { + static RangesUnion cases; + return cases; +} + +inline const RangesUnion& AllCases::fetch() { + if (!available_) { + build(); + } + return get_cases(); +} + +inline bool AllCases::is_available() const { + return available_; // no mutex required in one-way state +} + +} // namespace klotski::codec diff --git a/src/core/all_cases/basic_ranges.cc b/src/core/all_cases/internal/basic_ranges.cc similarity index 88% rename from src/core/all_cases/basic_ranges.cc rename to src/core/all_cases/internal/basic_ranges.cc index 209e26c..1ea319c 100644 --- a/src/core/all_cases/basic_ranges.cc +++ b/src/core/all_cases/internal/basic_ranges.cc @@ -1,7 +1,7 @@ #include #include -#include "all_cases.h" +#include "all_cases/all_cases.h" namespace klotski::cases { @@ -38,8 +38,7 @@ static void combine_sort(RangeIter begin, RangeIter mid, RangeIter end) noexcept } } -/// Spawn all ranges of specified conditions. -void BasicRanges::spawn_ranges(Ranges &ranges, int n1, int n2, int n3, int n4) noexcept { +void BasicRanges::spawn_ranges(Ranges &ranges, const int n1, const int n2, const int n3, const int n4) { auto num = n1 + n2 + n3 + n4; auto offset = (16 - num) << 1; // offset of low bits @@ -58,8 +57,7 @@ void BasicRanges::spawn_ranges(Ranges &ranges, int n1, int n2, int n3, int n4) n } while (next_permutation(series.begin(), series.end())); } -/// Search and sort all possible basic-ranges permutations. -void BasicRanges::build_ranges(Ranges &ranges) noexcept { +void BasicRanges::build_ranges(Ranges &ranges) { ranges.clear(); ranges.reserve(BASIC_RANGES_NUM); std::list flags {ranges.begin()}; // mark ordered interval @@ -82,8 +80,7 @@ void BasicRanges::build_ranges(Ranges &ranges) noexcept { } } -/// Execute the build process and ensure thread safety. -void BasicRanges::build() noexcept { +void BasicRanges::build() { if (available_) { return; // reduce consumption of mutex } diff --git a/src/core/all_cases/internal/basic_ranges.inl b/src/core/all_cases/internal/basic_ranges.inl new file mode 100644 index 0000000..d7850a3 --- /dev/null +++ b/src/core/all_cases/internal/basic_ranges.inl @@ -0,0 +1,21 @@ +#pragma once + +namespace klotski::cases { + +inline Ranges& BasicRanges::get_ranges() { + static Ranges ranges; + return ranges; +} + +inline const Ranges& BasicRanges::fetch() { + if (!available_) { + build(); + } + return get_ranges(); +} + +inline bool BasicRanges::is_available() const { + return available_; // no mutex required in one-way state +} + +} // namespace klotski::codec diff --git a/src/core/utils/utility.h b/src/core/utils/utility.h index 40264e6..d293298 100644 --- a/src/core/utils/utility.h +++ b/src/core/utils/utility.h @@ -2,14 +2,23 @@ #include -#define MARK_INSTANCE(T) \ - T(T &&) = delete; \ - T(const T &) = delete; \ - T& operator=(T &&) = delete; \ - T& operator=(const T &) = delete; +/// Mark target class as a singleton. +#define KLSK_INSTANCE(T) \ + private: \ + T() = default; \ + public: \ + T(T &&) = delete; \ + T(const T &) = delete; \ + T& operator=(T &&) = delete; \ + T& operator=(const T &) = delete; \ + static T& instance() { \ + static T ins; \ + return ins; \ + } namespace klotski { +/// Get the number of consecutive `0` in the low bits. inline int low_zero_num(const uint32_t bin) { return __builtin_ctzl(bin); @@ -19,6 +28,7 @@ inline int low_zero_num(const uint32_t bin) { // return __builtin_popcount(~(bin ^ -bin)) - 1; } +/// Get the number of consecutive `0` in the low bits. inline int low_zero_num(const uint64_t bin) { return __builtin_ctzll(bin); @@ -26,12 +36,13 @@ inline int low_zero_num(const uint64_t bin) { // return __builtin_popcount(~(bin ^ -bin)) - 1; } +/// Flips the input u32 every two bits in low-high symmetry. inline uint32_t range_reverse(uint32_t bin) { #if defined(__GNUC__) || defined(__clang__) bin = __builtin_bswap32(bin); - // TODO: using `std::byteswap` (c++23) #else // FIXME: `_byteswap_ulong` under MSVC + // TODO: using `std::byteswap` (c++23) bin = ((bin << 16) & 0xFFFF0000) | ((bin >> 16) & 0x0000FFFF); bin = ((bin << 8) & 0xFF00FF00) | ((bin >> 8) & 0x00FF00FF); #endif