diff --git a/.clang-format b/.clang-format index 332b39c..b05423f 100644 --- a/.clang-format +++ b/.clang-format @@ -7,6 +7,7 @@ PointerAlignment: Left UseTab: ForIndentation IndentWidth: 4 TabWidth: 4 +# ColumnLimit: 120 Cpp11BracedListStyle: true AlwaysBreakTemplateDeclarations: Yes AlwaysBreakAfterReturnType: All diff --git a/CMakeLists.txt b/CMakeLists.txt index b885da8..a564afe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,15 +1,20 @@ cmake_minimum_required(VERSION 3.13) project(Warthog - VERSION 0.2.1 + VERSION 0.5.0 LANGUAGES CXX C) +set_property(GLOBAL PROPERTY WARTHOG_warthog-core ON) + set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED TRUE) option(WARTHOG_INT128 "Enable support for __int128 on gcc and clang" OFF) +option(WARTHOG_BMI "Enable support cpu BMI for WARTHOG_INTRIN_HAS(BMI)" OFF) +option(WARTHOG_BMI2 "Enable support cpu BMI2 for WARTHOG_INTRIN_HAS(BMI2), use for Zen 3+" OFF) +option(WARTHOG_INTRIN_ALL "Enable march=native and support x86 intrinsics if able (based on system), supersedes all manual instruction sets" OFF) -include(cmake/submodule.cmake) +include(cmake/warthog.cmake) add_library(warthog_compile INTERFACE) add_library(warthog::compile ALIAS warthog_compile) diff --git a/LICENSE b/LICENSE index 2da8ca8..265f6da 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Shortest Path Lab @ Monash University +Copyright (c) 2024-2025 Shortest Path Lab @ Monash University Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 453f0b5..0179bcc 100644 --- a/README.md +++ b/README.md @@ -22,25 +22,104 @@ Requires `warthog-core` as a dependency. # Using warthog -Warthog dependencies are setup using git-submodules, use commands below are used to initialise and update respectively: - git submodule init - git submodule update +It is recommended to use warthog not as a fork, but included in an external repo. +This setup support FetchContent, git submodule and git subtree. -All dependencies should be placed in `/extern`. -If not project forked, we suggest setting the project up as below: +## CMake + +Setup a basic project using the following the commands: + + git init + git remote add warthog-core https://github.com/ShortestPathLab/warthog-core.git + git fetch warthog-core + git checkout warthog-core/main cmake/warthog.cmake + +Example `CMakeLists.txt`: -1. Copy `/cmake/submodule.cmake` to your repo -2. Submodule or subtree all your warthog dependencies and their dependencies into `/extern` -3. In `/CMakeLists.txt`, setup as below: ``` -include(cmake/submodule.cmake) -warthog_submodule(warthog-[module]) # repeat for each [module] +cmake_minimum_required(VERSION 3.13) + +project(App + VERSION 0.0.1 + LANGUAGES CXX C) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED TRUE) + +# warthog modules +include(cmake/warthog.cmake) + +warthog_module_declare(warthog-core v0.5.0) # is optional, remove for default of main +warthog_module(warthog-core) + +add_executable(app main.cpp) +target_link_libraries(app PUBLIC warthog::core) ``` -With (2) we flatten dependencies to top-level project. -With (3) `warthog_submodule` will add `add_subdirectoy` if your CMake is the top level config. +## Submodule + +Commands for adding a module as a submodules for each repo are found below: + + git submodule add https://github.com/ShortestPathLab/warthog-core.git extern/warthog-core + git submodule add https://github.com/ShortestPathLab/warthog-jps.git extern/warthog-jps + +To update the version of warthog, for warthog module `$module`: + + cd extern/$module + git fetch + git checkout|git switch + cd .. + git add $module + +This will update the submodule to the checkout commit. +Initialise or update the submodule on other clones with +the following commands: + + git submodule init # after clone + git submodule update # after pull -An informal project not designed to be a dependency does not require this, and can just `add_subdirectory` to all dependencies. +## Subtree + +Subtree will make the module a part of your repo, allowing +for local editing without forking. + +The setup for each module: + + git subtree -P extern/warthog-core add https://github.com/ShortestPathLab/warthog-core.git main|branch|commit --squash + git subtree -P extern/warthog-jps add https://github.com/ShortestPathLab/warthog-jps.git main|branch|commit --squash + +The update commands: + + git subtree -P extern/warthog-core pull https://github.com/ShortestPathLab/warthog-core.git main|branch|commit --squash + git subtree -P extern/warthog-jps pull https://github.com/ShortestPathLab/warthog-jps.git main|branch|commit --squash + +## Advance Module Details + +File `/cmake/warthog.cmake` from warthog core should be copied to user repo and `include` in CMake. +Calling `warthog_submodule(warthog-core)` will then add `warthog-core` to your CMake in the following order: +1. `add_subdirectory(/extern/warthog-core)` if `/extern/warthog-core/CMakeLists.txt` exists (submodule/subtree) +2. `FetchContent_Declare` then `FetchContent_MakeAvailable(warthog-core)` otherwise +3. Error if cannot find `warthog-core` content + +The `warthog_module` call only adds a module once, the following calls will be ignored. +The submodule/subtree version only works if called in the top level project by default; +if this method is preferred, then it should be added to the top level `/extern/`, can be overridden +with code `warthog_module(warthog-core ON)`. + +Declare of warthog-core can be done using the following code: +``` +warthog_module_declare(warthog-core [main|branch|tag|commit]) +``` +or: +``` +FetchContent_Declare(warthog-core + GIT_REPOSITORY https://github.com/ShortestPathLab/warthog-core.git + GIT_TAG [main|branch|tag|commit]) +``` +This will declare what warthog-core version to fetched. +The `warthog_module_declare` version makes it simple, although it only supports known warthog libraries. +The optional second parameter sets the version to pull, by default is `main` branch. +This system only support warthog 0.5 or greater. # Resources diff --git a/cmake/config.h.in b/cmake/config.h.in index 3319da7..b2c1f33 100644 --- a/cmake/config.h.in +++ b/cmake/config.h.in @@ -6,5 +6,8 @@ #define WARTHOG_VERSION_REVISON @CMAKE_PROJECT_VERSION_PATCH@ #cmakedefine WARTHOG_INT128 +#cmakedefine WARTHOG_INTRIN_ALL +#cmakedefine WARTHOG_BMI +#cmakedefine WARTHOG_BMI2 #endif // WARTHOG_APP_CONFIG_H diff --git a/cmake/headers.cmake b/cmake/headers.cmake index 570cbcf..b7d60c5 100644 --- a/cmake/headers.cmake +++ b/cmake/headers.cmake @@ -10,6 +10,7 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/warthog/config.h # use `find include/warthog/*/ -type f -name '*.h' | sort` target_sources(warthog_core PUBLIC include/warthog/constants.h +include/warthog/defines.h include/warthog/forward.h include/warthog/limits.h @@ -54,10 +55,12 @@ include/warthog/util/experiment.h include/warthog/util/file_utils.h include/warthog/util/gm_parser.h include/warthog/util/helpers.h +include/warthog/util/intrin.h include/warthog/util/log.h include/warthog/util/macros.h include/warthog/util/pqueue.h include/warthog/util/scenario_manager.h +include/warthog/util/template.h include/warthog/util/timer.h include/warthog/util/vec_io.h ) diff --git a/cmake/submodule.cmake b/cmake/submodule.cmake deleted file mode 100644 index da47f29..0000000 --- a/cmake/submodule.cmake +++ /dev/null @@ -1,35 +0,0 @@ -cmake_minimum_required(VERSION 3.13) - -# include this file to check submodule include - -set(WARTHOG_SUBMODULE_ROOT_ONLY ON CACHE BOOL "add submodule if present only allowed for root project") - -function(warthog_top_level) -set(_is_top OFF) -if(${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.21) - if(${PROJECT_IS_TOP_LEVEL}) - set(_is_top ON) - endif() -else() - if(${CMAKE_SOURCE_DIR} STREQUAL ${PROJECT_SOURCE_DIR}) - set(_is_top ON) - endif() -endif() -set(_is_top ${_is_top} PARENT_SCOPE) -endfunction() - -function(warthog_submodule dirname) -set(_is_top OFF) -if(NOT ${WARTHOG_SUBMODULE_ROOT_ONLY}) - set(_is_top ON) -else() - warthog_top_level() -endif() -if(${_is_top}) - if(NOT EXISTS "${PROJECT_SOURCE_DIR}/extern/${dirname}/.git") - message(SEND_ERROR "Failed to find submodule ${dirname}") - else() - add_subdirectory("${PROJECT_SOURCE_DIR}/extern/${dirname}" EXCLUDE_FROM_ALL) - endif() -endif() -endfunction() diff --git a/cmake/warthog.cmake b/cmake/warthog.cmake new file mode 100644 index 0000000..635f050 --- /dev/null +++ b/cmake/warthog.cmake @@ -0,0 +1,76 @@ +cmake_minimum_required(VERSION 3.13) + +# include this file to check submodule include + +# set here as only +set(warthog_https_warthog-core "https://github.com/ShortestPathLab/warthog-core.git" CACHE INTERNAL "") +set(warthog_https_warthog-jps "https://github.com/ShortestPathLab/warthog-jps.git" CACHE INTERNAL "") + +if(COMMAND warthog_module) + return() # do not redefine +endif() + +include(FetchContent) + +set(WARTHOG_MODULE_ROOT_ONLY ON CACHE BOOL "add submodule if present only allowed for root project") + +function(warthog_top_level) +set(_is_top OFF) +if(${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.21) + if(${PROJECT_IS_TOP_LEVEL}) + set(_is_top ON) + endif() +else() + if(${CMAKE_SOURCE_DIR} STREQUAL ${PROJECT_SOURCE_DIR}) + set(_is_top ON) + endif() +endif() +set(_warthog_is_top ${_is_top} PARENT_SCOPE) +endfunction() + +# warthog_module module [override_top] +# adds a module to warthog +# has checks to insure module is only added once +# if called from the top level project (or optional variable override_top is true) +# then if extern/${module}/CMakeLists.txt exists, adds that version instead +# intended for use with git module/subtree. +# otherwise, fetchcontent ${module} is made available +# user must add FetchContent_Declare +function(warthog_module module) +get_property(_module_added GLOBAL PROPERTY WARTHOG_${module} SET) +if(${_module_added}) # module already added + return() +endif() +if((${ARGC} GREATER 1) AND (${ARGV1})) + set(is_top_level ON) +else() + warthog_top_level() + set(is_top_level ${_warthog_is_top}) +endif() +if(${is_top_level}) + if(EXISTS "${PROJECT_SOURCE_DIR}/extern/${module}/CMakeLists.txt") + # module or subtree, include and exit + add_subdirectory("${PROJECT_SOURCE_DIR}/extern/${module}") + set_property(GLOBAL PROPERTY WARTHOG_${module} ON) + return() + endif() +endif() +# failed to add module, fetch if able +warthog_module_declare(${module}) +FetchContent_MakeAvailable(${module}) +set_property(GLOBAL PROPERTY WARTHOG_${module} ON) +endfunction() + +function(warthog_module_declare module) +if(NOT DEFINED warthog_https_${module}) + return() +endif() +if((${ARGC} GREATER 1)) + set(git_tag ${ARGV1}) +else() + set(git_tag "main") +endif() +FetchContent_Declare(${module} + GIT_REPOSITORY ${warthog_https_${module}} + GIT_TAG ${git_tag}) +endfunction() diff --git a/include/warthog/constants.h b/include/warthog/constants.h index 439f656..5f0bab3 100644 --- a/include/warthog/constants.h +++ b/include/warthog/constants.h @@ -55,6 +55,15 @@ struct identity_base return id == other.id; } + template + requires(!std::same_as>) + constexpr explicit + operator identity_base() const noexcept + { + auto alt = identity_base(static_cast(id)); + assert(id == alt.id || (is_none() && alt.is_none())); + return alt; + } constexpr explicit operator uint64_t() const noexcept { @@ -94,8 +103,8 @@ struct identity_base }; template constexpr bool is_identity_v = std::false_type{}; -template class T, class Tag, class IdType> -constexpr bool is_identity_v> = std::true_type{}; +template +constexpr bool is_identity_v> = std::true_type{}; template concept Identity = is_identity_v; @@ -121,14 +130,14 @@ constexpr uint32_t LOG2_DBWORD_BITS = std::popcount(DBWORD_BITS_MASK); // search and sort constants constexpr double DBL_ONE = 1.0; constexpr double DBL_TWO = 2.0; -constexpr double DBL_ROOT_TWO - = 1.414213562373095048801688724209698078569671875; -constexpr double DBL_ONE_OVER_TWO = 0.5; -constexpr double DBL_ONE_OVER_ROOT_TWO - = 0.70710678118654752440084436210484903928483593768847403658833986; -constexpr double DBL_ROOT_TWO_OVER_FOUR - = 0.35355339059327376220042218105242451964241796884423701829416993; -constexpr int32_t ONE = 100000; +// Truncate to allow for equality testing. +// Has around ~7 decimal places allowing up to 500k integer value before +// overflowing (6 hex digits = 24bits). +constexpr double DBL_ROOT_TWO = 0x1.6a09e6p0; +constexpr double DBL_ONE_OVER_TWO = 0.5; +constexpr double DBL_ONE_OVER_ROOT_TWO = 0x0.b504f3p0; +constexpr double DBL_ROOT_TWO_OVER_FOUR = DBL_ONE_OVER_TWO / 4; +constexpr int32_t ONE = 100000; constexpr uint32_t INF32 = std::numeric_limits::max(); // indicates uninitialised or diff --git a/include/warthog/domain/grid.h b/include/warthog/domain/grid.h index 74902a9..558b670 100644 --- a/include/warthog/domain/grid.h +++ b/include/warthog/domain/grid.h @@ -10,41 +10,214 @@ // #include +#include #include #include +#include +#include +#include namespace warthog::grid { +// TODO: document + +using grid_id = pad32_id; + typedef enum : uint8_t { - NORTH_ID, - SOUTH_ID, - EAST_ID, - WEST_ID, - NORTHEAST_ID, - NORTHWEST_ID, - SOUTHEAST_ID, - SOUTHWEST_ID, + NORTH_ID, // 0b000 + SOUTH_ID, // 0b001 + EAST_ID, // 0b010 + WEST_ID, // 0b011 + NORTHEAST_ID, // 0b100 + NORTHWEST_ID, // 0b101 + SOUTHEAST_ID, // 0b110 + SOUTHWEST_ID, // 0b111 + // only function with `secic` will consider the following + // second-intercardinal dirctions + NORTH_NORTHEAST_ID = NORTH_ID | (NORTHEAST_ID << 3), + EAST_NORTHEAST_ID = EAST_ID | (NORTHEAST_ID << 3), + NORTH_NORTHWEST_ID = NORTH_ID | (NORTHWEST_ID << 3), + WEST_NORTHWEST_ID = WEST_ID | (NORTHWEST_ID << 3), + SOUTH_SOUTHEAST_ID = SOUTH_ID | (SOUTHEAST_ID << 3), + EAST_SOUTHEAST_ID = EAST_ID | (SOUTHEAST_ID << 3), + SOUTH_SOUTHWEST_ID = SOUTH_ID | (SOUTHWEST_ID << 3), + WEST_SOUTHWEST_ID = WEST_ID | (SOUTHWEST_ID << 3), } direction_id; +static_assert( + NORTH_ID < 4 && SOUTH_ID < 4 && EAST_ID < 4 && WEST_ID < 4, + "cardinals id must be less than 4"); +static_assert( + NORTHEAST_ID >= 4 && NORTHWEST_ID >= 4 && SOUTHEAST_ID >= 4 + && SOUTHWEST_ID >= 4, + "intercardinals id must be at least 4"); +static_assert( + NORTHEAST_ID < 8 && NORTHWEST_ID < 8 && SOUTHEAST_ID < 8 + && SOUTHWEST_ID < 8, + "intercardinals id must be less than 8"); + +/// direction_id is NORTH_ID/SOUTH_ID/EAST_ID/WEST_ID +template +concept CardinalId + = D == NORTH_ID || D == EAST_ID || D == SOUTH_ID || D == WEST_ID; +/// direction_id is NORTHEAST_ID/NORTHWEST_ID/SOUTHEAST_ID/SOUTHWEST_ID +template +concept InterCardinalId = D == NORTHEAST_ID || D == NORTHWEST_ID + || D == SOUTHEAST_ID || D == SOUTHWEST_ID; +/// e.g. NORTH_NORTHEAST_ID +template +concept SecInterCardinalID = static_cast(D) >= 8; + +/// @return is NORTH_ID/SOUTH_ID/EAST_ID/WEST_ID +constexpr inline bool +is_cardinal_id(direction_id d) noexcept +{ + return static_cast(d) < 4; +} +/// @return is NORTHEAST_ID/NORTHWEST_ID/SOUTHEAST_ID/SOUTHWEST_ID +constexpr inline bool +is_intercardinal_id(direction_id d) noexcept +{ + return static_cast(d - 4) < 4; +} +/// @return is a intercardinal+cardinal combined direction +constexpr inline bool +is_secic_id(direction_id d) noexcept +{ + return static_cast(d) >= 8; +} +/// @return get the cardinal id from a combined direction +constexpr inline direction_id +secic_cardinal(direction_id d) noexcept +{ + direction_id c + = static_cast(static_cast(d) & 0b000'111); + assert(is_cardinal_id(c)); + return c; +} +/// @return get the intercardinal id from a combined direction +constexpr inline direction_id +secic_intercardinal(direction_id d) noexcept +{ + direction_id c = static_cast(static_cast(d) >> 3); + assert(is_intercardinal_id(c)); + return c; +} + typedef enum : uint8_t { - NONE = 0, - NORTH = 1 << NORTH_ID, - EAST = 1 << EAST_ID, - SOUTH = 1 << SOUTH_ID, - WEST = 1 << WEST_ID, - NORTHEAST = 1 << NORTHEAST_ID, - SOUTHEAST = 1 << SOUTHEAST_ID, - SOUTHWEST = 1 << SOUTHWEST_ID, - NORTHWEST = 1 << NORTHWEST_ID, - ALL = 255 + NONE = 0, + NORTH = 1 << NORTH_ID, + EAST = 1 << EAST_ID, + SOUTH = 1 << SOUTH_ID, + WEST = 1 << WEST_ID, + NORTHEAST = 1 << NORTHEAST_ID, + SOUTHEAST = 1 << SOUTHEAST_ID, + SOUTHWEST = 1 << SOUTHWEST_ID, + NORTHWEST = 1 << NORTHWEST_ID, + ALL = 255, + CARDINAL = NORTH | EAST | SOUTH | WEST, + INTERCARDINAL = NORTH | EAST | SOUTH | WEST, } direction; -// rotate direction cw +/// direction is NORTH/SOUTH/EAST/WEST +template +concept CardinalDir = D == NORTH || D == EAST || D == SOUTH || D == WEST; +/// direction is NORTHEAST/NORTHWEST/SOUTHEAST/SOUTHWEST +template +concept InterCardinalDir + = D == NORTHEAST || D == NORTHWEST || D == SOUTHEAST || D == SOUTHWEST; + +inline std::ostream& +operator<<(std::ostream& out, direction_id d) +{ + constexpr const char* OUTCHAR[8] + = {"N", "S", "E", "W", "NE", "NW", "SE", "SW"}; + if(d < 8) { out << OUTCHAR[d]; } + else { out << static_cast(d); } + return out; +} + +inline std::ostream& +operator<<(std::ostream& out, direction d) +{ + out << std::bitset<8>(d); + return out; +} + +/// @return convert direction_id to direction +constexpr direction +to_dir(direction_id id) noexcept +{ + assert(id < 8); + return static_cast(1 << id); +} +/// @return convert direction to direction_id +constexpr direction_id +to_dir_id(direction d) noexcept +{ + assert(std::popcount(static_cast(d)) == 1); + return static_cast( + std::countr_zero(static_cast(d))); +} + +/// @return EAST_ID/WEST_ID component from intercardinal id +constexpr direction_id +dir_intercardinal_hori(direction_id d) noexcept +{ + assert(is_intercardinal_id(d)); + if constexpr( + NORTHEAST_ID == 4 && NORTHWEST_ID == 5 && SOUTHEAST_ID == 6 + && SOUTHWEST_ID == 7) + { + // precise ordering, use math method + if constexpr(WEST_ID == EAST_ID + 1) + { // EAST_ID = 2, WEST_ID = 3 + return static_cast(EAST_ID + (d & 0b001)); + } + else { return (d & 0b001) ? WEST_ID : EAST_ID; } + } + else + { + constexpr uint16_t sel = ((uint16_t)(EAST_ID) << (NORTHEAST_ID << 1)) + | ((uint16_t)(EAST_ID) << (SOUTHEAST_ID << 1)) + | ((uint16_t)(WEST_ID) << (NORTHWEST_ID << 1)) + | ((uint16_t)(WEST_ID) << (SOUTHWEST_ID << 1)); + return static_cast((sel >> d * 2) & 0b11); + } +} + +/// @return NORTH_ID/SOUTH_ID component from intercardinal id constexpr direction_id -dir_id_cw(direction_id d) noexcept +dir_intercardinal_vert(direction_id d) noexcept +{ + assert(is_intercardinal_id(d)); + if constexpr( + NORTHEAST_ID == 4 && NORTHWEST_ID == 5 && SOUTHEAST_ID == 6 + && SOUTHWEST_ID == 7) + { + // precise ordering, use math method + if constexpr(SOUTH_ID == NORTH_ID + 1) + { // EAST_ID = 2, WEST_ID = 3 + return static_cast(NORTH_ID + ((d & 0b010) >> 1)); + } + else { return (d & 0b010) ? SOUTH_ID : NORTH_ID; } + } + else + { + constexpr uint16_t sel = ((uint16_t)(NORTH_ID) << (NORTHEAST_ID << 1)) + | ((uint16_t)(SOUTH_ID) << (SOUTHEAST_ID << 1)) + | ((uint16_t)(NORTH_ID) << (NORTHWEST_ID << 1)) + | ((uint16_t)(SOUTH_ID) << (SOUTHWEST_ID << 1)); + return static_cast((sel >> d * 2) & 0b11); + } +} + +/// @returns rotated 90 cw +constexpr direction_id +dir_id_cw90(direction_id d) noexcept { assert(static_cast(d) < 8); constexpr uint32_t sel = ((uint32_t)(NORTH_ID) << (WEST_ID << 2)) @@ -57,8 +230,9 @@ dir_id_cw(direction_id d) noexcept | ((uint32_t)(NORTHWEST_ID) << (SOUTHWEST_ID << 2)); return static_cast((sel >> d * 4) & 0b1111); } +/// @returns rotated 90 cw constexpr direction -dir_cw(direction d) noexcept +dir_cw90(direction d) noexcept { assert(std::popcount(static_cast(d)) == 1); constexpr uint64_t sel = ((uint64_t)(NORTH) << (WEST_ID << 3)) @@ -74,8 +248,42 @@ dir_cw(direction d) noexcept return static_cast(sel >> index * 8); } +/// @returns rotated 45 cw constexpr direction_id -dir_id_ccw(direction_id d) noexcept +dir_id_cw45(direction_id d) noexcept +{ + assert(static_cast(d) < 8); + constexpr uint32_t sel = ((uint32_t)(NORTH_ID) << (NORTHWEST_ID << 2)) + | ((uint32_t)(EAST_ID) << (NORTHEAST_ID << 2)) + | ((uint32_t)(SOUTH_ID) << (SOUTHEAST_ID << 2)) + | ((uint32_t)(WEST_ID) << (SOUTHWEST_ID << 2)) + | ((uint32_t)(NORTHEAST_ID) << (NORTH_ID << 2)) + | ((uint32_t)(SOUTHEAST_ID) << (EAST_ID << 2)) + | ((uint32_t)(SOUTHWEST_ID) << (SOUTH_ID << 2)) + | ((uint32_t)(NORTHWEST_ID) << (WEST_ID << 2)); + return static_cast((sel >> d * 4) & 0b1111); +} +/// @returns rotated 45 cw +constexpr direction +dir_cw45(direction d) noexcept +{ + assert(std::popcount(static_cast(d)) == 1); + constexpr uint64_t sel = ((uint64_t)(NORTH) << (NORTHWEST_ID << 3)) + | ((uint64_t)(EAST) << (NORTHEAST_ID << 3)) + | ((uint64_t)(SOUTH) << (SOUTHEAST_ID << 3)) + | ((uint64_t)(WEST) << (SOUTHWEST_ID << 3)) + | ((uint64_t)(NORTHEAST) << (NORTH_ID << 3)) + | ((uint64_t)(SOUTHEAST) << (EAST_ID << 3)) + | ((uint64_t)(SOUTHWEST) << (SOUTH_ID << 3)) + | ((uint64_t)(NORTHWEST) << (WEST_ID << 3)); + int index = std::countr_zero(static_cast( + d | 256u)); // |256u to ensure no branch in compiler + return static_cast(sel >> index * 8); +} + +/// @returns rotated 90 ccw +constexpr direction_id +dir_id_ccw90(direction_id d) noexcept { assert(static_cast(d) < 8); constexpr uint32_t sel = ((uint32_t)(NORTH_ID) << (EAST_ID << 2)) @@ -88,8 +296,9 @@ dir_id_ccw(direction_id d) noexcept | ((uint32_t)(NORTHWEST_ID) << (NORTHEAST_ID << 2)); return static_cast((sel >> d * 4) & 0b1111); } +/// @returns rotated 90 ccw constexpr direction -dir_ccw(direction d) noexcept +dir_ccw90(direction d) noexcept { assert(std::popcount(static_cast(d)) == 1); constexpr uint64_t sel = ((uint64_t)(NORTH) << (EAST_ID << 3)) @@ -105,6 +314,40 @@ dir_ccw(direction d) noexcept return static_cast(sel >> index * 8); } +/// @returns rotated 45 ccw +constexpr direction_id +dir_id_ccw45(direction_id d) noexcept +{ + assert(static_cast(d) < 8); + constexpr uint32_t sel = ((uint32_t)(NORTH_ID) << (NORTHEAST_ID << 2)) + | ((uint32_t)(EAST_ID) << (SOUTHEAST_ID << 2)) + | ((uint32_t)(SOUTH_ID) << (SOUTHWEST_ID << 2)) + | ((uint32_t)(WEST_ID) << (NORTHWEST_ID << 2)) + | ((uint32_t)(NORTHEAST_ID) << (EAST_ID << 2)) + | ((uint32_t)(SOUTHEAST_ID) << (SOUTH_ID << 2)) + | ((uint32_t)(SOUTHWEST_ID) << (WEST_ID << 2)) + | ((uint32_t)(NORTHWEST_ID) << (NORTH_ID << 2)); + return static_cast((sel >> d * 4) & 0b1111); +} +/// @returns rotated 45 ccw +constexpr direction +dir_ccw45(direction d) noexcept +{ + assert(std::popcount(static_cast(d)) == 1); + constexpr uint64_t sel = ((uint64_t)(NORTH) << (NORTHEAST_ID << 3)) + | ((uint64_t)(EAST) << (SOUTHEAST_ID << 3)) + | ((uint64_t)(SOUTH) << (SOUTHWEST_ID << 3)) + | ((uint64_t)(WEST) << (NORTHWEST_ID << 3)) + | ((uint64_t)(NORTHEAST) << (EAST_ID << 3)) + | ((uint64_t)(SOUTHEAST) << (SOUTH_ID << 3)) + | ((uint64_t)(SOUTHWEST) << (WEST_ID << 3)) + | ((uint64_t)(NORTHWEST) << (NORTH_ID << 3)); + int index = std::countr_zero(static_cast( + d | 256u)); // |256u to ensure no branch in compiler + return static_cast(sel >> index * 8); +} + +/// @return flip/rotated 180 constexpr direction_id dir_id_flip(direction_id d) noexcept { @@ -119,6 +362,7 @@ dir_id_flip(direction_id d) noexcept | ((uint32_t)(NORTHWEST_ID) << (SOUTHEAST_ID << 2)); return static_cast((sel >> d * 4) & 0b1111); } +/// @return flip/rotated 180 constexpr direction dir_flip(direction d) noexcept { @@ -136,6 +380,282 @@ dir_flip(direction d) noexcept return static_cast(sel >> index * 8); } +/// @brief gets the relative value to adjust a grid_id value for 1 unit in +/// direction d +/// @param width the width of the grid +/// @return the signed relative value stored unsigned +constexpr uint32_t +dir_id_adj(direction_id d, uint32_t width) noexcept +{ + assert(static_cast(d) < 8); + int32_t w = static_cast(width); + switch(d) + { + case NORTH_ID: + return static_cast(-w); + case SOUTH_ID: + return static_cast(w); + case EAST_ID: + return 1u; + case WEST_ID: + return -1u; + case NORTHEAST_ID: + return static_cast(-w + 1); + case NORTHWEST_ID: + return static_cast(-w - 1); + case SOUTHEAST_ID: + return static_cast(w + 1); + case SOUTHWEST_ID: + return static_cast(w - 1); + default: + assert(false); + return 0; + } +} +/// @brief converts dir_id_adj value into width, +/// only for intercardinal directions as EAST_ID/WEST_ID do not store +/// the width +/// @param d should match the value given to dir_id_adj +/// @return the signed relative value stored unsigned +constexpr uint32_t +dir_id_adj_inv_intercardinal(direction_id d, uint32_t a) noexcept +{ + assert(static_cast(d - 4) < 4); + switch(d) + { + case NORTHEAST_ID: + return static_cast(-static_cast(a) + 1); + case NORTHWEST_ID: + return static_cast(-static_cast(a) - 1); + case SOUTHEAST_ID: + return static_cast(a - 1); + case SOUTHWEST_ID: + return static_cast(a + 1); + default: + assert(false); + return 0; + } +} + +/// @brief gets the relative value to adjust a grid_id value 1 unit vertical +/// (north/south) of d +/// @param d direction of unit, uses the vertical component +/// @param width the width of the grid +/// @return the signed relative value stored unsigned, or 0 for EAST_ID/WEST_ID +constexpr uint32_t +dir_id_adj_vert(direction_id d, uint32_t width) noexcept +{ + assert(static_cast(d) < 8); + int32_t w = static_cast(width); + switch(d) + { + case NORTH_ID: + return static_cast(-w); + case SOUTH_ID: + return static_cast(w); + case EAST_ID: + return 0; + case WEST_ID: + return 0; + case NORTHEAST_ID: + return static_cast(-w); + case NORTHWEST_ID: + return static_cast(-w); + case SOUTHEAST_ID: + return static_cast(w); + case SOUTHWEST_ID: + return static_cast(w); + default: + assert(false); + return 0; + } +} +/// @brief gets the relative value to adjust a grid_id value 1 unit +/// horizontally (east/west) of d +/// @param d direction of unit, uses the horizontal component +/// @return the signed relative value stored unsigned, or 0 for EAST_ID/WEST_ID +constexpr int32_t +dir_id_adj_hori(direction_id d) noexcept +{ + assert(static_cast(d) < 8); + constexpr uint64_t sel = ((uint64_t)uint8_t(0u) << (NORTH_ID << 3)) + | ((uint64_t)uint8_t(0u) << (SOUTH_ID << 3)) + | ((uint64_t)uint8_t(1u) << (EAST_ID << 3)) + | ((uint64_t)uint8_t(-1u) << (WEST_ID << 3)) + | ((uint64_t)uint8_t(1u) << (NORTHEAST_ID << 3)) + | ((uint64_t)uint8_t(-1u) << (NORTHWEST_ID << 3)) + | ((uint64_t)uint8_t(1u) << (SOUTHEAST_ID << 3)) + | ((uint64_t)uint8_t(-1u) << (SOUTHWEST_ID << 3)); + return static_cast( + static_cast(static_cast(sel >> (d << 3)))); +} + +struct alignas(uint32_t) point +{ + uint16_t x; + uint16_t y; +}; +inline bool +operator==(point a, point b) +{ + return std::bit_cast(a) == std::bit_cast(b); +} + +/// @brief signed point, due to point allowing >2^15 numbers, does not support +/// point - point operations +struct alignas(uint32_t) spoint +{ + int16_t x; + int16_t y; + explicit constexpr + operator point() const noexcept + { + return point(static_cast(x), static_cast(y)); + } +}; +inline bool +operator==(spoint a, spoint b) +{ + return std::bit_cast(a) == std::bit_cast(b); +} + +constexpr inline std::pair +point_signed_diff(point a, point b) noexcept +{ + return { + static_cast( + static_cast(b.x) - static_cast(a.x)), + static_cast( + static_cast(b.y) - static_cast(a.y))}; +} + +constexpr inline point +operator+(point a, spoint b) noexcept +{ + return point{ + static_cast(a.x + static_cast(b.x)), + static_cast(a.y + static_cast(b.y))}; +} +constexpr inline spoint +operator+(spoint a, spoint b) noexcept +{ + return spoint{ + static_cast(a.x + b.x), static_cast(a.y + b.y)}; +} +constexpr inline spoint +operator*(int16_t a, spoint b) noexcept +{ + return spoint{ + static_cast(a * b.x), static_cast(a * b.y)}; +} + +/// @brief gets a unit signed-point in direction +/// @param d the direction for the unit-distance +/// @return the unit spoint +constexpr inline spoint +dir_unit_point(direction_id d) noexcept +{ + assert(static_cast(d) < 8); + union + { + uint32_t v; + spoint p; + } res; + // store point in 2 bits (10 = 1, 01 = 0, 00 = -1), then subtract 1 to pad + // packed_reldir stores each direction (8) in 4 bits, 0b3210 as 0byyxx + constexpr uint32_t packed_reldir = (0b0001 << NORTH_ID * 4) + | (0b1001 << SOUTH_ID * 4) | (0b0110 << EAST_ID * 4) + | (0b0100 << WEST_ID * 4) | (0b0010 << NORTHEAST_ID * 4) + | (0b0000 << NORTHWEST_ID * 4) | (0b1010 << SOUTHEAST_ID * 4) + | (0b1000 << SOUTHWEST_ID * 4); +#if WARTHOG_INTRIN_HAS(BMI2) + res.v = _pdep_u32(packed_reldir >> d * 4, 0xC000'C000u); +#else + res.p.x = (packed_reldir >> d * 4) & 0b11; + res.p.y = (packed_reldir >> (d * 4 + 2)) & 0b11; +#endif + res.p.x -= 1; + res.p.y -= 1; + return res.p; +} +/// @brief likes dir_unit_point, except direction_id can also be a secic +/// @param d the direction for unit-point, if a secic will use the +/// intercardinal component +/// @return the unit spoint +constexpr inline spoint +dir_unit_point_secic(direction_id d) noexcept +{ + assert(static_cast(d) < 8); + union + { + uint32_t v; + spoint p; + } res; + // store point in 2 bits (10 = 1, 01 = 0, 00 = -1), then subtract 1 to pad + // packed_reldir stores each direction (8) in 4 bits, 0b3210 as 0byyxx + constexpr uint32_t packed_reldir = (0b0001 << NORTH_ID * 4) + | (0b1001 << SOUTH_ID * 4) | (0b0110 << EAST_ID * 4) + | (0b0100 << WEST_ID * 4) | (0b0010 << NORTHEAST_ID * 4) + | (0b0000 << NORTHWEST_ID * 4) | (0b1010 << SOUTHEAST_ID * 4) + | (0b1000 << SOUTHWEST_ID * 4); + + // for second-intercardinal, return the intercardinal + d = d < 8 ? d : secic_intercardinal(d); +#if WARTHOG_INTRIN_HAS(BMI2) + res.v = _pdep_u32(packed_reldir >> d * 4, 0xC000'C000u); +#else + res.p.x = (packed_reldir >> d * 4) & 0b11; + res.p.y = (packed_reldir >> (d * 4 + 2)) & 0b11; +#endif + res.p.x -= 1; + res.p.y -= 1; + return res.p; +} + +/// @return the direction from p1 to p2. If diff x or diff y is zero, will be +/// cardinal, otherwise is intercardinal direction. +constexpr inline direction_id +point_to_direction_id(point p1, point p2) noexcept +{ + union + { + struct + { + int32_t x; + int32_t y; + } p; + uint64_t xy; + } c; + c.p.x = static_cast(p2.x) - static_cast(p1.x); + c.p.y = static_cast(p2.y) - static_cast(p1.y); + + if(c.p.x == 0) { return c.p.y >= 0 ? SOUTH_ID : NORTH_ID; } + else if(c.p.y == 0) { return c.p.x >= 0 ? EAST_ID : WEST_ID; } + else + { + // shift>> to mulitple of 4 (0b100) + // (x < 0) = 0b0100 + // (y < 0) = 0b1000 + // position sign bit as index + // equiv to ( (x < 0) | ((y < 0) << 1) ) * 4 + int shift = ((static_cast(c.p.x) >> (31 - 2)) & 0b0100) + | ((static_cast(c.p.y) >> (31 - 3)) & 0b1000); + assert( + (c.p.x > 0 && c.p.y > 0 && shift == 0) + || (c.p.x < 0 && c.p.y > 0 && shift == 4) + || (c.p.x > 0 && c.p.y < 0 && shift == 8) + || (c.p.x < 0 && c.p.y < 0 && shift == 12)); + // 4 bits to store direction_id + constexpr uint16_t intcard_dir + = (static_cast(SOUTHEAST_ID) << 0) + | (static_cast(SOUTHWEST_ID) << 4) + | (static_cast(NORTHEAST_ID) << 8) + | (static_cast(NORTHWEST_ID) << 12); + return static_cast( + static_cast((intcard_dir >> shift) & 0b1111)); + } +} + } // namespace warthog::grid #endif // WARTHOG_DOMAIN_GRID_H diff --git a/include/warthog/domain/gridmap.h b/include/warthog/domain/gridmap.h index e83c2ff..efeea4b 100644 --- a/include/warthog/domain/gridmap.h +++ b/include/warthog/domain/gridmap.h @@ -401,6 +401,22 @@ struct gridmap_slider loc += i; } + /// @return the unaligned 64b (8B) block from loc in little-endian format + uint64_t + get_block_64bit_le() const noexcept + { + uint64_t return_value; + std::memcpy(&return_value, loc, sizeof(uint64_t)); + + if constexpr(std::endian::native == std::endian::big) + { + // big endian, perform byte swap + return_value = util::byteswap_u64(return_value); + } + + return return_value; + } + // returns rows as: // [0] = middle // [1] = above (-y) diff --git a/include/warthog/memory/bittable.h b/include/warthog/memory/bittable.h index 2f9e28d..b361898 100644 --- a/include/warthog/memory/bittable.h +++ b/include/warthog/memory/bittable.h @@ -376,38 +376,38 @@ struct bittable : bitarray constexpr void set(id_type id, value_type value) noexcept { - assert(static_cast(id) < size()); + assert(size() == 0 || static_cast(id) < size()); bittable::bitarray::set(id, value); } constexpr void bit_and(id_type id, value_type value) noexcept { - assert(static_cast(id) < size()); + assert(size() == 0 || static_cast(id) < size()); bittable::bitarray::bit_and(id, value); } constexpr void bit_or(id_type id, value_type value) noexcept { - assert(static_cast(id) < size()); + assert(size() == 0 || static_cast(id) < size()); bittable::bitarray::bit_or(id, value); } constexpr void bit_xor(id_type id, value_type value) noexcept { - assert(static_cast(id) < size()); + assert(size() == 0 || static_cast(id) < size()); bittable::bitarray::bit_xor(id, value); } constexpr void bit_neg(id_type id) noexcept { - assert(static_cast(id) < size()); + assert(size() == 0 || static_cast(id) < size()); bittable::bitarray::bit_neg(id); } constexpr value_type get(id_type id) const noexcept { - assert(static_cast(id) < size()); + assert(size() == 0 || static_cast(id) < size()); return bittable::bitarray::get(id); } @@ -416,7 +416,7 @@ struct bittable : bitarray constexpr std::pair id_split(id_type id) const noexcept { - assert(static_cast(id) < size()); + assert(size() == 0 || static_cast(id) < size()); return bittable::bitarray::id_split(id); } diff --git a/include/warthog/search/expansion_policy.h b/include/warthog/search/expansion_policy.h index 25f88f8..00b2f84 100644 --- a/include/warthog/search/expansion_policy.h +++ b/include/warthog/search/expansion_policy.h @@ -33,6 +33,17 @@ class expansion_policy { return nodes_pool_size_; } + void + set_nodes_pool_size(size_t nodes_pool_size) + { + free(); + if(nodes_pool_size != 0) + { + nodes_pool_size_ = nodes_pool_size; + nodepool_ = new memory::node_pool(nodes_pool_size); + neis_ = new memory::arraylist(32); + } + } inline void reclaim() @@ -45,7 +56,22 @@ class expansion_policy reset() { current_ = 0; - neis_->clear(); + if(neis_) neis_->clear(); + } + + inline void + free() + { + reset(); + if(neis_) + { + assert(nodepool_ != nullptr); + delete neis_; + delete nodepool_; + neis_ = nullptr; + nodepool_ = nullptr; + nodes_pool_size_ = 0; + } } inline void @@ -218,11 +244,11 @@ class expansion_policy double cost_; }; - memory::node_pool* nodepool_; + memory::node_pool* nodepool_ = nullptr; // std::vector* neis_; - memory::arraylist* neis_; - uint32_t current_; - size_t nodes_pool_size_; + memory::arraylist* neis_ = nullptr; + uint32_t current_ = 0; + size_t nodes_pool_size_ = 0; }; } // namespace warthog::search diff --git a/include/warthog/search/gridmap_expansion_policy.h b/include/warthog/search/gridmap_expansion_policy.h index d99ac38..18a8408 100644 --- a/include/warthog/search/gridmap_expansion_policy.h +++ b/include/warthog/search/gridmap_expansion_policy.h @@ -35,6 +35,9 @@ class gridmap_expansion_policy_base : public expansion_policy public: gridmap_expansion_policy_base(domain::gridmap* map); + void + set_map(domain::gridmap& map); + search_problem_instance get_problem_instance(problem_instance* pi) override; @@ -70,7 +73,7 @@ class gridmap_expansion_policy_base : public expansion_policy mem() override; protected: - domain::gridmap* map_; + domain::gridmap* map_ = nullptr; }; class gridmap_expansion_policy : public gridmap_expansion_policy_base diff --git a/include/warthog/search/unidirectional_search.h b/include/warthog/search/unidirectional_search.h index 4fc9192..b8fec89 100644 --- a/include/warthog/search/unidirectional_search.h +++ b/include/warthog/search/unidirectional_search.h @@ -304,7 +304,11 @@ class unidirectional_search } trace(pi->verbose_, "Dominated;", *n); } - sol->met_.time_elapsed_nano_ = mytimer.elapsed_time_nano(); + if constexpr(FC == feasibility_criteria::until_cutoff) + { + // patched until AC FC RP reworked + sol->met_.time_elapsed_nano_ = mytimer.elapsed_time_nano(); + } } sol->met_.time_elapsed_nano_ = mytimer.elapsed_time_nano(); diff --git a/include/warthog/util/intrin.h b/include/warthog/util/intrin.h index 96a6113..010a6c9 100644 --- a/include/warthog/util/intrin.h +++ b/include/warthog/util/intrin.h @@ -17,7 +17,9 @@ #if defined(__x86_64__) && __has_include() #include #define WARTHOG_INTRIN -#define WARTHOG_INTRIN_HAS(inst) defined(__##inst##__) +#define WARTHOG_INTRIN_HAS(inst) \ + (defined(WARTHOG_##inst) \ + || (defined(WARTHOG_INTRIN_ALL) && defined(__##inst##__))) #else #define WARTHOG_INTRIN_HAS(inst) 0 #endif diff --git a/include/warthog/util/template.h b/include/warthog/util/template.h new file mode 100644 index 0000000..ce94e3d --- /dev/null +++ b/include/warthog/util/template.h @@ -0,0 +1,113 @@ +#ifndef WARTHOG_UTIL_TEMPLATE_H +#define WARTHOG_UTIL_TEMPLATE_H + +// template.h +// +// Utility file for template metaprogramming. +// +// @author: Ryan Hechenberger +// @created: 2025-06-27 +// + +#include + +namespace warthog::util +{ + +namespace details +{ + +template +struct for_each_integer_sequence; + +template +struct for_each_integer_sequence> +{ + template + static constexpr void + apply(TemplateFunc&& tfunc) + { + (..., tfunc(std::integral_constant())); + } + + template + static constexpr void + apply_if(IST value, TemplateFunc&& tfunc) + { + if constexpr(std::is_void_v) + { + apply_if_aux_( + value, std::forward(tfunc)); + } + } + template + static constexpr Ret + apply_if_aux_(IST value, TemplateFunc&& tfunc) + { + if(Arg0 == value) + { + return static_cast( + tfunc(std::integral_constant())); + } + else + { + return apply_if_aux_( + value, std::forward(tfunc)); + } + } + template + static constexpr Ret + apply_if_aux_(IST value, TemplateFunc&& tfunc) + { + return Ret(); + } +}; + +} // namespace details + +// template +// void for_each_integer_sequence(TemplateFunc&& tfunc); + +/// @brief takes an std::integer_sequence and pass each value to tfunc as a +/// std::integral_constant +/// @tparam IST loop over this std::integer_sequence +/// @param tfunc a function that takes an std::integral_constant of value from +/// IST +template +void +for_each_integer_sequence(TemplateFunc&& tfunc) +{ + details::for_each_integer_sequence::apply( + std::forward(tfunc)); +} + +/// @brief takes an std::integer_sequence and pass tfunc a +/// std::integral_constant that matches value +/// @tparam IST loop over this std::integer_sequence +/// @param value a value in IST to call tfunc on +/// @param tfunc a function that takes an std::integral_constant of value +template +void +choose_integer_sequence(auto value, TemplateFunc&& tfunc) +{ + details::for_each_integer_sequence::template apply_if( + value, std::forward(tfunc)); +} +/// @brief takes an std::integer_sequence and pass tfunc a +/// std::integral_constant and returns +/// @tparam IST loop over this std::integer_sequence +/// @param value a value in IST to call tfunc on +/// @param tfunc a function that takes an std::integral_constant of value +/// @return returns value from tfunc call, or Ret{} if value does not match any +/// IST +template +Ret +choose_integer_sequence(auto value, TemplateFunc&& tfunc) +{ + return details::for_each_integer_sequence::template apply_if( + value, std::forward(tfunc)); +} + +} // namespace warthog::util + +#endif // WARTHOG_UTIL_CAST_H diff --git a/src/search/expansion_policy.cpp b/src/search/expansion_policy.cpp index cf2b54c..8461a0a 100644 --- a/src/search/expansion_policy.cpp +++ b/src/search/expansion_policy.cpp @@ -5,18 +5,12 @@ namespace warthog::search expansion_policy::expansion_policy(size_t nodes_pool_size) { - nodes_pool_size_ = nodes_pool_size; - nodepool_ = new memory::node_pool(nodes_pool_size); - // neis_ = new std::vector(); - // neis_->reserve(32); - neis_ = new memory::arraylist(32); + set_nodes_pool_size(nodes_pool_size); } expansion_policy::~expansion_policy() { - reset(); - delete neis_; - delete nodepool_; + free(); } } // namespace warthog::search diff --git a/src/search/gridmap_expansion_policy.cpp b/src/search/gridmap_expansion_policy.cpp index 5892f8b..f60470b 100644 --- a/src/search/gridmap_expansion_policy.cpp +++ b/src/search/gridmap_expansion_policy.cpp @@ -11,9 +11,17 @@ namespace warthog::search gridmap_expansion_policy_base::gridmap_expansion_policy_base( domain::gridmap* map) - : expansion_policy(map->height() * map->width()), map_(map) + : expansion_policy(map != nullptr ? (map->height() * map->width()) : 0), + map_(map) { } +void +gridmap_expansion_policy_base::set_map(domain::gridmap& map) +{ + map_ = ↦ + set_nodes_pool_size(map.height() * map.width()); +} + search_problem_instance gridmap_expansion_policy_base::get_problem_instance(problem_instance* pi) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c0d8fe4..574693b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,3 +1,4 @@ cmake_minimum_required(VERSION 3.13) add_subdirectory(memory) +add_subdirectory(units) diff --git a/tests/units/CMakeLists.txt b/tests/units/CMakeLists.txt new file mode 100644 index 0000000..174d520 --- /dev/null +++ b/tests/units/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.13) + +add_executable(warthog_test_units grid.cxx) +target_link_libraries(warthog_test_units Catch2::Catch2WithMain warthog::core) +catch_discover_tests(warthog_test_units) diff --git a/tests/units/grid.cxx b/tests/units/grid.cxx new file mode 100644 index 0000000..c5aa148 --- /dev/null +++ b/tests/units/grid.cxx @@ -0,0 +1,200 @@ +#include +#include +#include +#include +#include +#include + +TEST_CASE("grid utility tests", "[unit][grid]") +{ + using namespace warthog::grid; + // const cardinals in clockwise order + constexpr std::array Cardinal{NORTH, EAST, SOUTH, WEST}; + constexpr std::array ICardinal{ + NORTHEAST, SOUTHEAST, SOUTHWEST, NORTHWEST}; + constexpr std::array CardinalID{ + NORTH_ID, EAST_ID, SOUTH_ID, WEST_ID}; + constexpr std::array ICardinalID{ + NORTHEAST_ID, SOUTHEAST_ID, SOUTHWEST_ID, NORTHWEST_ID}; + + SECTION("cardinals and intercardinals checks and conversions") + { + // Cardinal and InterCardinals are checked correctly + for(auto d : CardinalID) + { + CHECK(is_cardinal_id(d)); + CHECK_FALSE(is_intercardinal_id(d)); + } + for(auto d : ICardinalID) + { + CHECK(is_intercardinal_id(d)); + CHECK_FALSE(is_cardinal_id(d)); + } + + // convert betweein direction and direction_id + for(int i = 0; i < 4; ++i) + { + CHECK(to_dir(CardinalID[i]) == Cardinal[i]); + CHECK(to_dir_id(Cardinal[i]) == CardinalID[i]); + CHECK(to_dir(ICardinalID[i]) == ICardinal[i]); + CHECK(to_dir_id(ICardinal[i]) == ICardinalID[i]); + } + } + + SECTION("directions rotate") + { + // rotate directions correctly + for(int i = 0; i < 4; ++i) + { + // CW 90 + CHECK(dir_id_cw90(CardinalID[i]) == CardinalID[(i + 1) % 4]); + CHECK(dir_id_cw90(ICardinalID[i]) == ICardinalID[(i + 1) % 4]); + CHECK(dir_cw90(Cardinal[i]) == Cardinal[(i + 1) % 4]); + CHECK(dir_cw90(ICardinal[i]) == ICardinal[(i + 1) % 4]); + + // CCW 90 + CHECK(dir_id_ccw90(CardinalID[i]) == CardinalID[(i + 3) % 4]); + CHECK(dir_id_ccw90(ICardinalID[i]) == ICardinalID[(i + 3) % 4]); + CHECK(dir_ccw90(Cardinal[i]) == Cardinal[(i + 3) % 4]); + CHECK(dir_ccw90(ICardinal[i]) == ICardinal[(i + 3) % 4]); + + // flip/180 + CHECK(dir_id_flip(CardinalID[i]) == CardinalID[(i + 2) % 4]); + CHECK(dir_id_flip(ICardinalID[i]) == ICardinalID[(i + 2) % 4]); + CHECK(dir_flip(Cardinal[i]) == Cardinal[(i + 2) % 4]); + CHECK(dir_flip(ICardinal[i]) == ICardinal[(i + 2) % 4]); + + // CW 45 + CHECK(dir_id_cw45(CardinalID[i]) == ICardinalID[i]); + CHECK(dir_id_cw45(ICardinalID[i]) == CardinalID[(i + 1) % 4]); + CHECK(dir_cw45(Cardinal[i]) == ICardinal[i]); + CHECK(dir_cw45(ICardinal[i]) == Cardinal[(i + 1) % 4]); + + // CCW 45 + CHECK(dir_id_ccw45(CardinalID[i]) == ICardinalID[(i + 3) % 4]); + CHECK(dir_id_ccw45(ICardinalID[i]) == CardinalID[i]); + CHECK(dir_ccw45(Cardinal[i]) == ICardinal[(i + 3) % 4]); + CHECK(dir_ccw45(ICardinal[i]) == Cardinal[i]); + } + } + + SECTION("directions grid_id adjust") + { +#define WART_DG_WIDTH 164 + // direction adjust + CHECK(dir_id_adj(NORTH_ID, WART_DG_WIDTH) == -WART_DG_WIDTH); + CHECK(dir_id_adj(EAST_ID, WART_DG_WIDTH) == 1); + CHECK(dir_id_adj(SOUTH_ID, WART_DG_WIDTH) == WART_DG_WIDTH); + CHECK(dir_id_adj(WEST_ID, WART_DG_WIDTH) == -1); + CHECK(dir_id_adj(NORTHEAST_ID, WART_DG_WIDTH) == -WART_DG_WIDTH + 1); + CHECK(dir_id_adj(SOUTHEAST_ID, WART_DG_WIDTH) == WART_DG_WIDTH + 1); + CHECK(dir_id_adj(SOUTHWEST_ID, WART_DG_WIDTH) == WART_DG_WIDTH - 1); + CHECK(dir_id_adj(NORTHWEST_ID, WART_DG_WIDTH) == -WART_DG_WIDTH - 1); + + // invert intercardinal results + for(auto d : ICardinalID) + { + CHECK( + dir_id_adj_inv_intercardinal(d, dir_id_adj(d, WART_DG_WIDTH)) + == WART_DG_WIDTH); + } + + // vertical adjust + CHECK(dir_id_adj_vert(NORTH_ID, WART_DG_WIDTH) == -WART_DG_WIDTH); + CHECK(dir_id_adj_vert(EAST_ID, WART_DG_WIDTH) == 0); + CHECK(dir_id_adj_vert(SOUTH_ID, WART_DG_WIDTH) == WART_DG_WIDTH); + CHECK(dir_id_adj_vert(WEST_ID, WART_DG_WIDTH) == 0); + CHECK(dir_id_adj_vert(NORTHEAST_ID, WART_DG_WIDTH) == -WART_DG_WIDTH); + CHECK(dir_id_adj_vert(SOUTHEAST_ID, WART_DG_WIDTH) == WART_DG_WIDTH); + CHECK(dir_id_adj_vert(SOUTHWEST_ID, WART_DG_WIDTH) == WART_DG_WIDTH); + CHECK(dir_id_adj_vert(NORTHWEST_ID, WART_DG_WIDTH) == -WART_DG_WIDTH); + + // horizontal adjust + CHECK(dir_id_adj_hori(NORTH_ID) == 0); + CHECK(dir_id_adj_hori(EAST_ID) == 1); + CHECK(dir_id_adj_hori(SOUTH_ID) == 0); + CHECK(dir_id_adj_hori(WEST_ID) == -1); + CHECK(dir_id_adj_hori(NORTHEAST_ID) == 1); + CHECK(dir_id_adj_hori(SOUTHEAST_ID) == 1); + CHECK(dir_id_adj_hori(SOUTHWEST_ID) == -1); + CHECK(dir_id_adj_hori(NORTHWEST_ID) == -1); + + // check dir_id_adj = dir_id_adj_vert + dir_id_adj_hori + for(auto d : CardinalID) + { + CHECK( + dir_id_adj_vert(d, WART_DG_WIDTH) + dir_id_adj_hori(d) + == dir_id_adj(d, WART_DG_WIDTH)); + } + for(auto d : ICardinalID) + { + CHECK( + dir_id_adj_vert(d, WART_DG_WIDTH) + dir_id_adj_hori(d) + == dir_id_adj(d, WART_DG_WIDTH)); + } + } + + SECTION("directions to points") + { + CHECK(dir_unit_point(NORTH_ID) == spoint(0, -1)); + CHECK(dir_unit_point(EAST_ID) == spoint(1, 0)); + CHECK(dir_unit_point(SOUTH_ID) == spoint(0, 1)); + CHECK(dir_unit_point(WEST_ID) == spoint(-1, 0)); + CHECK(dir_unit_point(NORTHEAST_ID) == spoint(1, -1)); + CHECK(dir_unit_point(SOUTHEAST_ID) == spoint(1, 1)); + CHECK(dir_unit_point(SOUTHWEST_ID) == spoint(-1, 1)); + CHECK(dir_unit_point(NORTHWEST_ID) == spoint(-1, -1)); + } + + SECTION("points to direction") + { + auto i = GENERATE(take(100, random(uint32_t(0), uint32_t(10'000)))); + auto j = GENERATE(take(100, random(uint32_t(0), uint32_t(10'000)))); + point a( + static_cast(i % 100), static_cast(i / 100)); + point b( + static_cast(j % 100), static_cast(j / 100)); + + auto [dx, dy] = point_signed_diff(a, b); + REQUIRE(dx == static_cast(b.x) - static_cast(a.x)); + REQUIRE(dy == static_cast(b.y) - static_cast(a.y)); + direction_id res = point_to_direction_id(a, b); + REQUIRE(static_cast(res) < 8); + if(dx == 0) + { + // NORTH/SOUTH + if(dy < 0) + CHECK(res == NORTH_ID); + else if(dy > 0) + CHECK(res == SOUTH_ID); + else + CHECK(a == b); + } + else if(dy == 0) + { + // EAST/WEST + if(dx < 0) + CHECK(res == WEST_ID); + else if(dx > 0) + CHECK(res == EAST_ID); + else + CHECK(a == b); // should never reach + } + else if(dy < 0) + { + // NE/NW + if(dx < 0) + CHECK(res == NORTHWEST_ID); + else + CHECK(res == NORTHEAST_ID); + } + else + { // dy < 0 + // SE/SW + if(dx < 0) + CHECK(res == SOUTHWEST_ID); + else + CHECK(res == SOUTHEAST_ID); + } + } +}