diff --git a/3rdParty/tl/CMakeLists.txt b/3rdParty/tl/CMakeLists.txt new file mode 100644 index 000000000..63dc97a7e --- /dev/null +++ b/3rdParty/tl/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(tl INTERFACE) + +target_include_directories(tl INTERFACE ${CMAKE_CURRENT_LIST_DIR}) diff --git a/3rdParty/tl/function_ref.hpp b/3rdParty/tl/function_ref.hpp new file mode 100644 index 000000000..d8326833e --- /dev/null +++ b/3rdParty/tl/function_ref.hpp @@ -0,0 +1,216 @@ +/// +// function_ref - A low-overhead non-owning function +// Written in 2017 by Simon Brand (@TartanLlama) +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to the +// public domain worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. If not, see +// . +/// + +#ifndef TL_FUNCTION_REF_HPP +#define TL_FUNCTION_REF_HPP + +#define TL_FUNCTION_REF_VERSION_MAJOR 1 +#define TL_FUNCTION_REF_VERSION_MINOR 0 +#define TL_FUNCTION_REF_VERSION_PATCH 0 + +#if (defined(_MSC_VER) && _MSC_VER == 1900) +/// \exclude +#define TL_FUNCTION_REF_MSVC2015 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +/// \exclude +#define TL_FUNCTION_REF_GCC49 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 && \ + !defined(__clang__)) +/// \exclude +#define TL_FUNCTION_REF_GCC54 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +// GCC < 5 doesn't support overloading on const&& for member functions +/// \exclude +#define TL_FUNCTION_REF_NO_CONSTRR +#endif + +#if __cplusplus > 201103L +/// \exclude +#define TL_FUNCTION_REF_CXX14 +#endif + +// constexpr implies const in C++11, not C++14 +#if (__cplusplus == 201103L || defined(TL_FUNCTION_REF_MSVC2015) || \ + defined(TL_FUNCTION_REF_GCC49)) && \ + !defined(TL_FUNCTION_REF_GCC54) +/// \exclude +#define TL_FUNCTION_REF_11_CONSTEXPR +#else +/// \exclude +#define TL_FUNCTION_REF_11_CONSTEXPR constexpr +#endif + +#include +#include + +namespace tl { +namespace detail { +namespace fnref { +// C++14-style aliases for brevity +template using remove_const_t = typename std::remove_const::type; +template +using remove_reference_t = typename std::remove_reference::type; +template using decay_t = typename std::decay::type; +template +using enable_if_t = typename std::enable_if::type; +template +using conditional_t = typename std::conditional::type; + +// std::invoke from C++17 +// https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround +template >::value>, + int = 0> +constexpr auto invoke(Fn &&f, Args &&... args) noexcept( + noexcept(std::mem_fn(f)(std::forward(args)...))) + -> decltype(std::mem_fn(f)(std::forward(args)...)) { + return std::mem_fn(f)(std::forward(args)...); +} + +template >{}>> +constexpr auto invoke(Fn &&f, Args &&... args) noexcept( + noexcept(std::forward(f)(std::forward(args)...))) + -> decltype(std::forward(f)(std::forward(args)...)) { + return std::forward(f)(std::forward(args)...); +} + +// std::invoke_result from C++17 +template struct invoke_result_impl; + +template +struct invoke_result_impl< + F, decltype(tl::detail::fnref::invoke(std::declval(), std::declval()...), void()), + Us...> { + using type = decltype(tl::detail::fnref::invoke(std::declval(), std::declval()...)); +}; + +template +using invoke_result = invoke_result_impl; + +template +using invoke_result_t = typename invoke_result::type; + +template +struct is_invocable_r_impl : std::false_type {}; + +template +struct is_invocable_r_impl< + typename std::is_convertible, R>::type, R, F, Args...> + : std::true_type {}; + +template +using is_invocable_r = is_invocable_r_impl; + +} // namespace detail +} // namespace fnref + +/// A lightweight non-owning reference to a callable. +/// +/// Example usage: +/// +/// ```cpp +/// void foo (function_ref func) { +/// std::cout << "Result is " << func(21); //42 +/// } +/// +/// foo([](int i) { return i*2; }); +template class function_ref; + +/// Specialization for function types. +template class function_ref { +public: + constexpr function_ref() noexcept = delete; + + /// Creates a `function_ref` which refers to the same callable as `rhs`. + constexpr function_ref(const function_ref &rhs) noexcept = default; + + /// Constructs a `function_ref` referring to `f`. + /// + /// \synopsis template constexpr function_ref(F &&f) noexcept + template , function_ref>::value && + detail::fnref::is_invocable_r::value> * = nullptr> + TL_FUNCTION_REF_11_CONSTEXPR function_ref(F &&f) noexcept + : obj_(const_cast(reinterpret_cast(std::addressof(f)))) { + callback_ = [](void *obj, Args... args) -> R { + return detail::fnref::invoke( + *reinterpret_cast::type>(obj), + std::forward(args)...); + }; + } + + /// Makes `*this` refer to the same callable as `rhs`. + TL_FUNCTION_REF_11_CONSTEXPR function_ref & + operator=(const function_ref &rhs) noexcept = default; + + /// Makes `*this` refer to `f`. + /// + /// \synopsis template constexpr function_ref &operator=(F &&f) noexcept; + template ::value> + * = nullptr> + TL_FUNCTION_REF_11_CONSTEXPR function_ref &operator=(F &&f) noexcept { + obj_ = reinterpret_cast(std::addressof(f)); + callback_ = [](void *obj, Args... args) { + return detail::fnref::invoke( + *reinterpret_cast::type>(obj), + std::forward(args)...); + }; + + return *this; + } + + /// Swaps the referred callables of `*this` and `rhs`. + constexpr void swap(function_ref &rhs) noexcept { + std::swap(obj_, rhs.obj_); + std::swap(callback_, rhs.callback_); + } + + /// Call the stored callable with the given arguments. + R operator()(Args... args) const { + return callback_(obj_, std::forward(args)...); + } + +private: + void *obj_ = nullptr; + R (*callback_)(void *, Args...) = nullptr; +}; + +/// Swaps the referred callables of `lhs` and `rhs`. +template +constexpr void swap(function_ref &lhs, + function_ref &rhs) noexcept { + lhs.swap(rhs); +} + +#if __cplusplus >= 201703L +template +function_ref(R (*)(Args...))->function_ref; + +// TODO, will require some kind of callable traits +// template +// function_ref(F) -> function_ref; +#endif +} // namespace tl + +#endif diff --git a/CMake/Dependencies.cmake b/CMake/Dependencies.cmake index 6ef3041fe..fd23477ee 100644 --- a/CMake/Dependencies.cmake +++ b/CMake/Dependencies.cmake @@ -185,6 +185,8 @@ endif() add_subdirectory(3rdParty/libmpq) +add_subdirectory(3rdParty/tl) + add_subdirectory(3rdParty/hoehrmann_utf8) add_subdirectory(3rdParty/PKWare) diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 6fe558764..e07a6dedb 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -248,6 +248,7 @@ target_link_libraries(libdevilutionx PUBLIC libmpq libsmackerdec simpleini::simpleini + tl hoehrmann_utf8 ) diff --git a/Source/debug.cpp b/Source/debug.cpp index 72f68007e..f6709bd0e 100644 --- a/Source/debug.cpp +++ b/Source/debug.cpp @@ -723,7 +723,7 @@ std::string DebugCmdSpawnUniqueMonster(const string_view parameter) int spawnedMonster = 0; - auto ret = Crawl(0, MaxCrawlRadius, [&](auto displacement) -> std::optional { + auto ret = Crawl(0, MaxCrawlRadius, [&](Displacement displacement) -> std::optional { Point pos = myPlayer.position.tile + displacement; if (dPlayer[pos.x][pos.y] != 0 || dMonster[pos.x][pos.y] != 0) return {}; @@ -809,7 +809,7 @@ std::string DebugCmdSpawnMonster(const string_view parameter) int spawnedMonster = 0; - auto ret = Crawl(0, MaxCrawlRadius, [&](auto displacement) -> std::optional { + auto ret = Crawl(0, MaxCrawlRadius, [&](Displacement displacement) -> std::optional { Point pos = myPlayer.position.tile + displacement; if (dPlayer[pos.x][pos.y] != 0 || dMonster[pos.x][pos.y] != 0) return {}; diff --git a/Source/engine/path.cpp b/Source/engine/path.cpp index 3505cc593..afb477763 100644 --- a/Source/engine/path.cpp +++ b/Source/engine/path.cpp @@ -436,7 +436,7 @@ bool path_solid_pieces(Point startPosition, Point destinationPosition) std::optional FindClosestValidPosition(const std::function &posOk, Point startingPosition, unsigned int minimumRadius, unsigned int maximumRadius) { - return Crawl(minimumRadius, maximumRadius, [&](auto displacement) -> std::optional { + return Crawl(minimumRadius, maximumRadius, [&](Displacement displacement) -> std::optional { Point candidatePosition = startingPosition + displacement; if (posOk(candidatePosition)) return candidatePosition; diff --git a/Source/lighting.cpp b/Source/lighting.cpp index cae9512de..9deadb79c 100644 --- a/Source/lighting.cpp +++ b/Source/lighting.cpp @@ -128,8 +128,68 @@ void DoUnLight(int nXPos, int nYPos, int nRadius) } } +bool CrawlFlipsX(Displacement mirrored, tl::function_ref function) +{ + for (const Displacement displacement : { mirrored.flipX(), mirrored }) { + if (!function(displacement)) + return false; + } + return true; +} + +bool CrawlFlipsY(Displacement mirrored, tl::function_ref function) +{ + for (const Displacement displacement : { mirrored, mirrored.flipY() }) { + if (!function(displacement)) + return false; + } + return true; +} + +bool CrawlFlipsXY(Displacement mirrored, tl::function_ref function) +{ + for (const Displacement displacement : { mirrored.flipX(), mirrored, mirrored.flipXY(), mirrored.flipY() }) { + if (!function(displacement)) + return false; + } + return true; +} + } // namespace +bool DoCrawl(unsigned radius, tl::function_ref function) +{ + if (radius == 0) + return function(Displacement { 0, 0 }); + + if (!CrawlFlipsY({ 0, static_cast(radius) }, function)) + return false; + for (unsigned i = 1; i < radius; i++) { + if (!CrawlFlipsXY({ static_cast(i), static_cast(radius) }, function)) + return false; + } + if (radius > 1) { + if (!CrawlFlipsXY({ static_cast(radius) - 1, static_cast(radius) - 1 }, function)) + return false; + } + if (!CrawlFlipsX({ static_cast(radius), 0 }, function)) + return false; + for (unsigned i = 1; i < radius; i++) { + if (!CrawlFlipsXY({ static_cast(radius), static_cast(i) }, function)) + return false; + } + return true; +} + +bool DoCrawl(unsigned minRadius, unsigned maxRadius, tl::function_ref function) +{ + for (unsigned i = minRadius; i <= maxRadius; i++) { + if (!DoCrawl(i, function)) + return false; + } + return true; +} + void DoLighting(Point position, int nRadius, int lnum) { int xoff = 0; diff --git a/Source/lighting.h b/Source/lighting.h index 8a6d3f76a..42c72224d 100644 --- a/Source/lighting.h +++ b/Source/lighting.h @@ -8,12 +8,15 @@ #include #include +#include + #include "automap.h" #include "engine.h" #include "engine/point.hpp" #include "miniwin/miniwin.h" #include "utils/attributes.h" #include "utils/stdcompat/invoke_result_t.hpp" +#include "utils/stdcompat/optional.hpp" namespace devilution { @@ -75,42 +78,6 @@ void ChangeVisionXY(int id, Point position); void ProcessVisionList(); void lighting_color_cycling(); -template -auto CrawlFlipsX(Displacement mirrored, F function) -> invoke_result_t -{ - const Displacement Flips[] = { mirrored.flipX(), mirrored }; - for (auto displacement : Flips) { - auto ret = function(displacement); - if (ret) - return ret; - } - return {}; -} - -template -auto CrawlFlipsY(Displacement mirrored, F function) -> invoke_result_t -{ - const Displacement Flips[] = { mirrored, mirrored.flipY() }; - for (auto displacement : Flips) { - auto ret = function(displacement); - if (ret) - return ret; - } - return {}; -} - -template -auto CrawlFlipsXY(Displacement mirrored, F function) -> invoke_result_t -{ - const Displacement Flips[] = { mirrored.flipX(), mirrored, mirrored.flipXY(), mirrored.flipY() }; - for (auto displacement : Flips) { - auto ret = function(displacement); - if (ret) - return ret; - } - return {}; -} - constexpr int MaxCrawlRadius = 18; /** @@ -137,45 +104,29 @@ constexpr int MaxCrawlRadius = 18; * +-------> x */ +bool DoCrawl(unsigned radius, tl::function_ref function); +bool DoCrawl(unsigned minRadius, unsigned maxRadius, tl::function_ref function); + template auto Crawl(unsigned radius, F function) -> invoke_result_t { - if (radius == 0) - return function(Displacement { 0, 0 }); - - auto ret = CrawlFlipsY({ 0, static_cast(radius) }, function); - if (ret) - return ret; - for (unsigned i = 1; i < radius; i++) { - ret = CrawlFlipsXY({ static_cast(i), static_cast(radius) }, function); - if (ret) - return ret; - } - if (radius > 1) { - ret = CrawlFlipsXY({ static_cast(radius) - 1, static_cast(radius) - 1 }, function); - if (ret) - return ret; - } - ret = CrawlFlipsX({ static_cast(radius), 0 }, function); - if (ret) - return ret; - for (unsigned i = 1; i < radius; i++) { - ret = CrawlFlipsXY({ static_cast(radius), static_cast(i) }, function); - if (ret) - return ret; - } - return {}; + invoke_result_t result; + DoCrawl(radius, [&result, &function](Displacement displacement) -> bool { + result = function(displacement); + return !result; + }); + return result; } template auto Crawl(unsigned minRadius, unsigned maxRadius, F function) -> invoke_result_t { - for (unsigned i = minRadius; i <= maxRadius; i++) { - auto displacement = Crawl(i, function); - if (displacement) - return displacement; - } - return {}; + invoke_result_t result; + DoCrawl(minRadius, maxRadius, [&result, &function](Displacement displacement) -> bool { + result = function(displacement); + return !result; + }); + return result; } /* rdata */ diff --git a/Source/missiles.cpp b/Source/missiles.cpp index db34138cd..99d89e098 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -1218,7 +1218,7 @@ void AddJester(Missile &missile, const AddMissileParameter ¶meter) void AddStealPotions(Missile &missile, const AddMissileParameter & /*parameter*/) { - Crawl(0, 2, [&](auto displacement) { + Crawl(0, 2, [&](Displacement displacement) { Point target = missile.position.start + displacement; if (!InDungeonBounds(target)) return false; @@ -3020,7 +3020,7 @@ void MI_FireRing(Missile &missile) if (missile.limitReached) return; - Crawl(3, [&](auto displacement) { + Crawl(3, [&](Displacement displacement) { Point target = Point { missile.var1, missile.var2 } + displacement; if (!InDungeonBounds(target)) return false; @@ -3376,7 +3376,7 @@ void MI_Chain(Missile &missile) Direction dir = GetDirection(position, dst); AddMissile(position, dst, dir, MIS_LIGHTCTRL, TARGET_MONSTERS, id, 1, missile._mispllvl); int rad = std::min(missile._mispllvl + 3, MaxCrawlRadius); - Crawl(1, rad, [&](auto displacement) { + Crawl(1, rad, [&](Displacement displacement) { Point target = position + displacement; if (InDungeonBounds(target) && dMonster[target.x][target.y] > 0) { dir = GetDirection(position, target); diff --git a/test/lighting_test.cpp b/test/lighting_test.cpp index b0204655a..a3b5ddaae 100644 --- a/test/lighting_test.cpp +++ b/test/lighting_test.cpp @@ -13,7 +13,7 @@ TEST(Lighting, CrawlTables) int x = 20; int y = 20; - Crawl(0, MaxCrawlRadius, [&](auto displacement) { + Crawl(0, MaxCrawlRadius, [&](Displacement displacement) { int dx = x + displacement.deltaX; int dy = y + displacement.deltaY; EXPECT_EQ(added[dx][dy], false) << "displacement " << displacement.deltaX << ":" << displacement.deltaY << " added twice";