diff --git a/Source/engine/render/clx_render.cpp b/Source/engine/render/clx_render.cpp index 9d75f3560..cf068911e 100644 --- a/Source/engine/render/clx_render.cpp +++ b/Source/engine/render/clx_render.cpp @@ -6,12 +6,16 @@ #include "clx_render.hpp" #include +#include #include +#include "engine/point.hpp" #include "engine/render/blit_impl.hpp" #include "engine/render/scrollrt.h" #include "utils/attributes.h" #include "utils/clx_decode.hpp" +#include "utils/static_bit_vector.hpp" +#include "utils/static_vector.hpp" #ifdef DEBUG_CLX #include @@ -250,420 +254,159 @@ void DoRenderBackwards( } } -template -DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderOutlineForPixel(uint8_t *dst, int dstPitch, uint8_t color) -{ - if constexpr (North) - dst[-dstPitch] = color; - if constexpr (West) - dst[-1] = color; - if constexpr (East) - dst[1] = color; - if constexpr (South) - dst[dstPitch] = color; -} - -template -DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderOutlineForPixel(uint8_t *dst, int dstPitch, uint8_t srcColor, uint8_t color) -{ - if constexpr (SkipColorIndexZero) { - if (srcColor == 0) - return; - } - RenderOutlineForPixel(dst, dstPitch, color); -} - -template -DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderOutlineForPixels(uint8_t *dst, int dstPitch, int width, uint8_t color) -{ - if constexpr (North) - std::memset(dst - dstPitch, color, width); - - if constexpr (West && East) - std::memset(dst - 1, color, width + 2); - else if constexpr (West) - std::memset(dst - 1, color, width); - else if constexpr (East) - std::memset(dst + 1, color, width); - - if constexpr (South) - std::memset(dst + dstPitch, color, width); -} +constexpr size_t MaxOutlinePixels = 1024; +constexpr size_t MaxOutlineSpriteWidth = 256; +using OutlinePixels = StaticVector, MaxOutlinePixels>; +using OutlineRowSolidRuns = StaticVector, MaxOutlineSpriteWidth / 2 + 1>; -template -DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderOutlineForPixels(uint8_t *dst, int dstPitch, int width, const uint8_t *src, uint8_t color) +void PopulateOutlinePixelsForRow( + const StaticBitVector &rowBelow, + const StaticBitVector &rowAbove, + const OutlineRowSolidRuns &solidRuns, + int y, OutlinePixels &result) { - if constexpr (SkipColorIndexZero) { - while (width-- > 0) - RenderOutlineForPixel(dst++, dstPitch, *src++, color); - } else { - RenderOutlineForPixels(dst, dstPitch, width, color); - } -} - -template -void RenderClxOutlinePixelsCheckFirstColumn( - uint8_t *dst, int dstPitch, int dstX, - const uint8_t *src, uint8_t width, uint8_t color) -{ - if (dstX == -1) { - if constexpr (Fill) { - RenderOutlineForPixel( - dst++, dstPitch, color); - } else { - RenderOutlineForPixel( - dst++, dstPitch, *src++, color); - } - --width; - } - if (width > 0) { - if constexpr (Fill) { - RenderOutlineForPixel(dst++, dstPitch, color); - } else { - RenderOutlineForPixel(dst++, dstPitch, *src++, color); - } - --width; - } - if (width > 0) { - if constexpr (Fill) { - RenderOutlineForPixels(dst, dstPitch, width, color); - } else { - RenderOutlineForPixels(dst, dstPitch, width, src, color); - } - } -} - -template -void RenderClxOutlinePixelsCheckLastColumn( - uint8_t *dst, int dstPitch, int dstX, int dstW, - const uint8_t *src, uint8_t width, uint8_t color) -{ - const bool lastPixel = dstX != dstW; - const bool oobPixel = dstX + width == dstW + 1; - const int numSpecialPixels = (lastPixel ? 1 : 0) + (oobPixel ? 1 : 0); - if (width > numSpecialPixels) { - width -= numSpecialPixels; - if constexpr (Fill) { - RenderOutlineForPixels(dst, dstPitch, width, color); - } else { - RenderOutlineForPixels(dst, dstPitch, width, src, color); - src += width; - } - dst += width; - } - if (lastPixel) { - if constexpr (Fill) { - RenderOutlineForPixel(dst++, dstPitch, color); - } else { - RenderOutlineForPixel(dst++, dstPitch, *src++, color); - } - } - if (oobPixel) { - if constexpr (Fill) { - RenderOutlineForPixel(dst, dstPitch, color); - } else { - RenderOutlineForPixel(dst, dstPitch, *src, color); + for (auto [xBegin, xEnd] : solidRuns) { + result.emplace_back(static_cast(xBegin - 1), static_cast(y)); + for (int xi = xBegin; xi < xEnd; ++xi) { + if (!rowAbove.test(xi)) { + result.emplace_back(static_cast(xi), static_cast(y - 1)); + } + if (!rowBelow.test(xi)) { + result.emplace_back(static_cast(xi), static_cast(y + 1)); + } } + result.emplace_back(static_cast(xEnd), static_cast(y)); } } -template -void RenderClxOutlinePixels( - uint8_t *dst, int dstPitch, int dstX, int dstW, - const uint8_t *src, uint8_t width, uint8_t color) +void AppendOutlineRowSolidRuns(int x, int w, OutlineRowSolidRuns &solidRuns) { - if constexpr (SkipColorIndexZero && Fill) { - if (*src == 0) - return; - } - - if constexpr (CheckFirstColumn) { - if (dstX <= 0) { - RenderClxOutlinePixelsCheckFirstColumn( - dst, dstPitch, dstX, src, width, color); - return; - } - } - if constexpr (CheckLastColumn) { - if (dstX + width >= dstW) { - RenderClxOutlinePixelsCheckLastColumn( - dst, dstPitch, dstX, dstW, src, width, color); - return; - } - } - - if constexpr (Fill) { - RenderOutlineForPixels(dst, dstPitch, width, color); + if (solidRuns.empty() || solidRuns.back().second != x) { + solidRuns.emplace_back(x, x + w); } else { - RenderOutlineForPixels(dst, dstPitch, width, src, color); + solidRuns.back().second = static_cast(x + w); } } -template -const uint8_t *RenderClxOutlineRowClipped( // NOLINT(readability-function-cognitive-complexity) - const Surface &out, Point position, const uint8_t *src, std::size_t srcWidth, - ClipX clipX, uint8_t color, SkipSize &skipSize) +template +OutlinePixels GetOutline(ClxSprite sprite) // NOLINT(readability-function-cognitive-complexity) { - int_fast16_t remainingWidth = clipX.width; - uint8_t v; + const unsigned width = sprite.width(); + assert(width < MaxOutlineSpriteWidth); + OutlinePixels result; + StaticBitVector rows[3] = { + StaticBitVector(width), + StaticBitVector(width), + StaticBitVector(width) + }; + StaticBitVector *rowAbove = &rows[0]; + StaticBitVector *row = &rows[1]; + StaticBitVector *rowBelow = &rows[2]; - auto *dst = &out[position]; - const auto dstPitch = out.pitch(); + int x = 0; + int y = sprite.height() - 1; - const auto renderPixels = [&](bool fill, uint8_t w) { - if (fill) { - RenderClxOutlinePixels( - dst, dstPitch, position.x, out.w(), src, w, color); - ++src; - } else { - RenderClxOutlinePixels( - dst, dstPitch, position.x, out.w(), src, w, color); - src += v; - } - dst += w; - }; + OutlineRowSolidRuns solidRuns[2]; + OutlineRowSolidRuns *solidRunAbove = &solidRuns[0]; + OutlineRowSolidRuns *solidRun = &solidRuns[1]; - if constexpr (ClipWidth) { - auto remainingLeftClip = clipX.left - skipSize.xOffset; - if (skipSize.xOffset > clipX.left) { - position.x += static_cast(skipSize.xOffset - clipX.left); - dst += skipSize.xOffset - clipX.left; - } - while (remainingLeftClip > 0) { - v = static_cast(*src++); + const uint8_t *src = sprite.pixelData(); + const uint8_t *const end = src + sprite.pixelDataSize(); + while (src < end) { + while (x < static_cast(width)) { + const auto v = static_cast(*src++); + uint8_t w; if (IsClxOpaque(v)) { - const bool fill = IsClxOpaqueFill(v); - v = fill ? GetClxOpaqueFillWidth(v) : GetClxOpaquePixelsWidth(v); - if (v > remainingLeftClip) { - const uint8_t overshoot = v - remainingLeftClip; - renderPixels(fill, overshoot); - position.x += overshoot; + if constexpr (SkipColorIndexZero) { + if (IsClxOpaqueFill(v)) { + w = GetClxOpaqueFillWidth(v); + const auto color = static_cast(*src++); + if (color != 0) { + AppendOutlineRowSolidRuns(x, w, *solidRunAbove); + } + } else { + w = GetClxOpaquePixelsWidth(v); + bool prevZero = solidRunAbove->empty() || solidRunAbove->back().second != x; + for (unsigned i = 0; i < w; ++i) { + const auto color = static_cast(src[i]); + if (color == 0) { + if (!prevZero) ++solidRunAbove->back().second; + prevZero = true; + } else { + if (prevZero) solidRunAbove->emplace_back(x + i, x + i); + ++solidRunAbove->back().second; + prevZero = false; + } + } + src += w; + } } else { - src += fill ? 1 : v; + if (IsClxOpaqueFill(v)) { + w = GetClxOpaqueFillWidth(v); + ++src; + } else { + w = GetClxOpaquePixelsWidth(v); + src += w; + } + AppendOutlineRowSolidRuns(x, w, *solidRunAbove); } } else { - if (v > remainingLeftClip) { - const uint8_t overshoot = v - remainingLeftClip; - dst += overshoot; - position.x += overshoot; - } + w = v; } - remainingLeftClip -= v; + x += w; } - remainingWidth += remainingLeftClip; - } else { - position.x += static_cast(skipSize.xOffset); - dst += skipSize.xOffset; - remainingWidth -= skipSize.xOffset; - } - while (remainingWidth > 0) { - v = static_cast(*src++); - if (IsClxOpaque(v)) { - const bool fill = IsClxOpaqueFill(v); - v = fill ? GetClxOpaqueFillWidth(v) : GetClxOpaquePixelsWidth(v); - if constexpr (ClipWidth) { - renderPixels(fill, std::min(remainingWidth, static_cast(v))); - } else { - renderPixels(fill, v); + for (const auto &[xBegin, xEnd] : *solidRunAbove) { + rowAbove->set(xBegin, xEnd - xBegin); + } + PopulateOutlinePixelsForRow(*rowBelow, *rowAbove, *solidRun, y + 1, result); + + // (0, 1, 2) => (2, 0, 1) + std::swap(row, rowBelow); + std::swap(row, rowAbove); + rowAbove->reset(); + + std::swap(solidRunAbove, solidRun); + solidRunAbove->clear(); + + if (x > static_cast(width)) { + // Transparent overrun. + const unsigned numWholeTransparentLines = x / width; + if (numWholeTransparentLines > 1) { + PopulateOutlinePixelsForRow(*rowBelow, *rowAbove, *solidRun, y, result); + solidRun->clear(); + row->reset(); } + if (numWholeTransparentLines > 2) rowBelow->reset(); + y -= static_cast(numWholeTransparentLines); + x = static_cast(x % width); } else { - dst += v; - } - remainingWidth -= v; - position.x += v; - } - - if constexpr (ClipWidth) { - remainingWidth += clipX.right; - if (remainingWidth > 0) { - skipSize.xOffset = static_cast(srcWidth) - remainingWidth; - return SkipRestOfLineWithOverrun(src, static_cast(srcWidth), skipSize); + --y; + x = 0; } } - skipSize = GetSkipSize(remainingWidth, static_cast(srcWidth)); - - return src; + rowAbove->reset(); + PopulateOutlinePixelsForRow(*rowBelow, *rowAbove, *solidRun, y + 1, result); + return result; } template -void RenderClxOutlineClippedY(const Surface &out, Point position, RenderSrc src, // NOLINT(readability-function-cognitive-complexity) - uint8_t color) +void RenderClxOutline(const Surface &out, Point position, ClxSprite sprite, uint8_t color) { - // Skip the bottom clipped lines. - const int dstHeight = out.h(); - SkipSize skipSize = { 0, SkipLinesForRenderBackwardsWithOverrun(position, src, dstHeight) }; - if (src.begin == src.end) - return; - - const ClipX clipX = { 0, 0, static_cast(src.width) }; - - if (position.y == dstHeight) { - // After-bottom line - can only draw north. - src.begin = RenderClxOutlineRowClipped( - out, position, src.begin, src.width, clipX, color, skipSize); - position.y -= static_cast(skipSize.wholeLines); - } - if (src.begin == src.end) - return; - - if (position.y + 1 == dstHeight) { - // Bottom line - cannot draw south. - src.begin = RenderClxOutlineRowClipped( - out, position, src.begin, src.width, clipX, color, skipSize); - position.y -= static_cast(skipSize.wholeLines); - } - - while (position.y > 0 && src.begin != src.end) { - src.begin = RenderClxOutlineRowClipped( - out, position, src.begin, src.width, clipX, color, skipSize); - position.y -= static_cast(skipSize.wholeLines); - } - if (src.begin == src.end) - return; - - if (position.y == 0) { - src.begin = RenderClxOutlineRowClipped( - out, position, src.begin, src.width, clipX, color, skipSize); - position.y -= static_cast(skipSize.wholeLines); - } - if (src.begin == src.end) - return; - - if (position.y == -1) { - // Special case: the top of the sprite is 1px below the last line, render just the outline above. - RenderClxOutlineRowClipped( - out, position, src.begin, src.width, clipX, color, skipSize); - } -} - -template -void RenderClxOutlineClippedXY(const Surface &out, Point position, RenderSrc src, // NOLINT(readability-function-cognitive-complexity) - uint8_t color) -{ - // Skip the bottom clipped lines. - const int dstHeight = out.h(); - SkipSize skipSize = { 0, SkipLinesForRenderBackwardsWithOverrun(position, src, dstHeight) }; - if (src.begin == src.end) - return; - - ClipX clipX = CalculateClipX(position.x, src.width, out); - if (clipX.width < 0) - return; - if (clipX.left > 0) { - --clipX.left, ++clipX.width; - } else if (clipX.right > 0) { - --clipX.right, ++clipX.width; - } - position.x += static_cast(clipX.left); - - if (position.y == dstHeight) { - // After-bottom line - can only draw north. - if (position.x <= 0) { - src.begin = RenderClxOutlineRowClipped(out, position, src.begin, src.width, clipX, color, skipSize); - } else if (position.x + clipX.width >= out.w()) { - src.begin = RenderClxOutlineRowClipped(out, position, src.begin, src.width, clipX, color, skipSize); - } else { - src.begin = RenderClxOutlineRowClipped(out, position, src.begin, src.width, clipX, color, skipSize); - } - position.y -= static_cast(skipSize.wholeLines); - } - if (src.begin == src.end) - return; - - if (position.y + 1 == dstHeight) { - // Bottom line - cannot draw south. - if (position.x <= 0) { - src.begin = RenderClxOutlineRowClipped( - out, position, src.begin, src.width, clipX, color, skipSize); - } else if (position.x + clipX.width >= out.w()) { - src.begin = RenderClxOutlineRowClipped( - out, position, src.begin, src.width, clipX, color, skipSize); - } else { - src.begin = RenderClxOutlineRowClipped( - out, position, src.begin, src.width, clipX, color, skipSize); - } - position.y -= static_cast(skipSize.wholeLines); - } - - if (position.x <= 0) { - while (position.y > 0 && src.begin != src.end) { - src.begin = RenderClxOutlineRowClipped( - out, position, src.begin, src.width, clipX, color, skipSize); - position.y -= static_cast(skipSize.wholeLines); - } - } else if (position.x + clipX.width >= out.w()) { - while (position.y > 0 && src.begin != src.end) { - src.begin = RenderClxOutlineRowClipped( - out, position, src.begin, src.width, clipX, color, skipSize); - position.y -= static_cast(skipSize.wholeLines); + const OutlinePixels pixels = GetOutline(sprite); + position.y -= sprite.height() - 1; + if (position.x > 0 && position.x + sprite.width() < out.w() + && position.y > 0 && position.y + sprite.height() < out.h()) { + for (auto [x, y] : pixels) { + *out.at(position.x + x, position.y + y) = color; } } else { - while (position.y > 0 && src.begin != src.end) { - src.begin = RenderClxOutlineRowClipped( - out, position, src.begin, src.width, clipX, color, skipSize); - position.y -= static_cast(skipSize.wholeLines); - } - } - if (src.begin == src.end) - return; - - if (position.y == 0) { - if (position.x <= 0) { - src.begin = RenderClxOutlineRowClipped( - out, position, src.begin, src.width, clipX, color, skipSize); - } else if (position.x + clipX.width >= out.w()) { - src.begin = RenderClxOutlineRowClipped( - out, position, src.begin, src.width, clipX, color, skipSize); - } else { - src.begin = RenderClxOutlineRowClipped( - out, position, src.begin, src.width, clipX, color, skipSize); - } - position.y -= static_cast(skipSize.wholeLines); - } - if (src.begin == src.end) - return; - - if (position.y == -1) { - // Before-top line - can only draw south. - if (position.x <= 0) { - src.begin = RenderClxOutlineRowClipped(out, position, src.begin, src.width, clipX, color, skipSize); - } else if (position.x + clipX.width >= out.w()) { - src.begin = RenderClxOutlineRowClipped(out, position, src.begin, src.width, clipX, color, skipSize); - } else { - src.begin = RenderClxOutlineRowClipped(out, position, src.begin, src.width, clipX, color, skipSize); + for (auto [x, y] : pixels) { + out.SetPixel(Point(position.x + x, position.y + y), color); } } } -template -void RenderClxOutline(const Surface &out, Point position, const uint8_t *src, std::size_t srcSize, - std::size_t srcWidth, uint8_t color) -{ - RenderSrc srcForBackwards { src, src + srcSize, static_cast(srcWidth) }; - if (position.x > 0 && position.x + static_cast(srcWidth) < static_cast(out.w())) { - RenderClxOutlineClippedY(out, position, srcForBackwards, color); - } else { - RenderClxOutlineClippedXY(out, position, srcForBackwards, color); - } -} - void ClxApplyTrans(ClxSprite sprite, const uint8_t *trn) { // A bit of a hack but this is the only place in the code where we need mutable sprites. @@ -840,12 +583,12 @@ void ClxDrawBlendedTRN(const Surface &out, Point position, ClxSprite clx, const void ClxDrawOutline(const Surface &out, uint8_t col, Point position, ClxSprite clx) { - RenderClxOutline(out, position, clx.pixelData(), clx.pixelDataSize(), clx.width(), col); + RenderClxOutline(out, position, clx, col); } void ClxDrawOutlineSkipColorZero(const Surface &out, uint8_t col, Point position, ClxSprite clx) { - RenderClxOutline(out, position, clx.pixelData(), clx.pixelDataSize(), clx.width(), col); + RenderClxOutline(out, position, clx, col); } } // namespace devilution diff --git a/Source/utils/static_bit_vector.hpp b/Source/utils/static_bit_vector.hpp new file mode 100644 index 000000000..964c93058 --- /dev/null +++ b/Source/utils/static_bit_vector.hpp @@ -0,0 +1,92 @@ +#include +#include +#include + +#include "utils/attributes.h" + +namespace devilution { + +/** + * @brief A stack-allocated bit-vector with a fixed capacity and a fixed size < capacity. + * + * @tparam N capacity. + */ +template // NOLINT(google-runtime-int) +class StaticBitVector { +public: + explicit StaticBitVector(size_t size) + : size_(size) + , num_words_(size / BitsPerWord + ((size % BitsPerWord) == 0 ? 0 : 1)) + { + } + + [[nodiscard]] bool test(size_t pos) const { return (word(pos) & maskBit(pos)) != 0; } + + void set() { std::fill_n(words_, num_words_, ~Word { 0 }); } + void set(size_t pos) { word(pos) |= maskBit(pos); } + + // Sets all bits in the [begin, begin+length) range to 1. + // Length must not be zero. + void set(size_t begin, size_t length) + { + DVL_ASSUME(length != 0); + const size_t firstWord = begin / BitsPerWord; + const size_t lastWord = (begin + length - 1) / BitsPerWord; + const Word firstWordMask = onesFrom(begin); + const Word lastWordMask = onesUpTo(begin + length - 1); + if (firstWord == lastWord) { + words_[firstWord] |= firstWordMask & lastWordMask; + return; + } + words_[firstWord] |= firstWordMask; + std::fill(&words_[firstWord + 1], &words_[lastWord], ~Word { 0 }); + words_[lastWord] |= lastWordMask; + } + + void reset() { std::fill_n(words_, num_words_, Word { 0 }); } + void reset(size_t pos) { word(pos) &= ~maskBit(pos); } + + // Sets all bits in the [begin, begin+length) range to 0. + // Length must not be zero. + void reset(size_t begin, size_t length) + { + DVL_ASSUME(length != 0); + const size_t firstWord = begin / BitsPerWord; + const size_t lastWord = (begin + length - 1) / BitsPerWord; + const Word firstWordMask = onesFrom(begin); + const Word lastWordMask = onesUpTo(begin + length - 1); + if (firstWord == lastWord) { + words_[firstWord] &= ~(firstWordMask & lastWordMask); + return; + } + words_[firstWord] &= ~firstWordMask; + std::fill(&words_[firstWord + 1], &words_[lastWord], Word { 0 }); + words_[lastWord] &= ~lastWordMask; + } + +private: + static constexpr Word onesFrom(size_t begin) + { + return (~Word { 0 }) << (begin % BitsPerWord); + } + static constexpr Word onesUpTo(size_t last) + { + return (~Word { 0 }) >> (BitsPerWord - (last % BitsPerWord) - 1); + } + + static constexpr Word maskBit(size_t pos) + { + return Word { 1 } << (pos % BitsPerWord); + } + Word word(size_t pos) const { return words_[pos / BitsPerWord]; } + Word &word(size_t pos) { return words_[pos / BitsPerWord]; } + + static constexpr unsigned BitsPerWord = CHAR_BIT * sizeof(Word); + static constexpr unsigned MaxNumWords = N / BitsPerWord + ((N % BitsPerWord) == 0 ? 0 : 1); + Word words_[MaxNumWords] = {}; + + size_t size_; + size_t num_words_; +}; + +} // namespace devilution diff --git a/Source/utils/static_vector.hpp b/Source/utils/static_vector.hpp index bf89e5ba4..92c7ebccb 100644 --- a/Source/utils/static_vector.hpp +++ b/Source/utils/static_vector.hpp @@ -75,6 +75,14 @@ public: return *data_[pos].ptr(); } + void clear() + { + for (std::size_t pos = 0; pos < size_; ++pos) { + std::destroy_at(data_[pos].ptr()); + } + size_ = 0; + } + ~StaticVector() { for (std::size_t pos = 0; pos < size_; ++pos) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index cf5266c0f..53cfd46ee 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -44,17 +44,29 @@ set(tests utf8_test writehero_test ) +set( + standalone_tests + static_bit_vector_test +) include(Fixtures.cmake) -foreach(test_target ${tests}) +foreach(test_target ${tests} ${standalone_tests}) add_executable(${test_target} "${test_target}.cpp") gtest_discover_tests(${test_target}) - target_link_libraries(${test_target} PRIVATE test_main) set_target_properties(${test_target} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) if(GPERF) target_link_libraries(${test_target} PUBLIC ${GPERFTOOLS_LIBRARIES}) endif() endforeach() +foreach(test_target ${tests}) + target_link_libraries(${test_target} PRIVATE test_main) +endforeach() + +foreach(test_target ${standalone_tests}) + target_link_libraries(${test_target} GTest::gtest_main) + target_include_directories(${test_target} PRIVATE "${PROJECT_SOURCE_DIR}/Source") +endforeach() + target_include_directories(writehero_test PRIVATE ../3rdParty/PicoSHA2) diff --git a/test/static_bit_vector_test.cpp b/test/static_bit_vector_test.cpp new file mode 100644 index 000000000..bd7501303 --- /dev/null +++ b/test/static_bit_vector_test.cpp @@ -0,0 +1,30 @@ +#include + +#include "utils/static_bit_vector.hpp" + +namespace devilution { +namespace { + +TEST(StaticBitVectorTest, SetSingleTest) +{ + StaticBitVector<500> v(100); + EXPECT_FALSE(v.test(97)); + v.set(97); + EXPECT_TRUE(v.test(97)); +} + +TEST(StaticBitVectorTest, SetRangeTest) +{ + StaticBitVector<500> v(100); + EXPECT_FALSE(v.test(97)); + v.set(50, 50); + for (size_t i = 0; i < 50; ++i) { + EXPECT_FALSE(v.test(i)) << i; + } + for (size_t i = 50; i < 100; ++i) { + EXPECT_TRUE(v.test(i)) << i; + } +} + +} // namespace +} // namespace devilution