Give til::bitmap custom allocator support and add til::pmr::bitmap (#8787)
`til::details::bitmap<Allocator>` will use `Allocator` for its `dynamic_bitset`, and it will use a rebound allocator for its run storage. Allocator should be an allocator type storing `unsigned long long`, the backing store type for `dynamic_bitset`. I've introduced a type alias, `til::bitmap`, which papers over the allocator choice for all existing code. I've also introduced a second type alias, `til::pmr::bitmap`, which lets a consumer use the C++ polymorphic allocator system. I chatted with @miniksa about whether to keep the "full" allocator version in `details` or not. We decided that for the simplicity of the `til` namespace, we would. If anybody has a compelling reason to use `til::details::bitmap<Allocator>` directly, we can re-evaluate this decision.
This commit is contained in:
parent
6c4878c8d5
commit
2919d96c21
|
@ -11,6 +11,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
|||
{
|
||||
namespace details
|
||||
{
|
||||
template<typename Allocator>
|
||||
class _bitmap_const_iterator
|
||||
{
|
||||
public:
|
||||
|
@ -20,7 +21,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
|||
using pointer = typename const til::rectangle*;
|
||||
using reference = typename const til::rectangle&;
|
||||
|
||||
_bitmap_const_iterator(const dynamic_bitset<>& values, til::rectangle rc, ptrdiff_t pos) :
|
||||
_bitmap_const_iterator(const dynamic_bitset<unsigned long long, Allocator>& values, til::rectangle rc, ptrdiff_t pos) :
|
||||
_values(values),
|
||||
_rc(rc),
|
||||
_pos(pos),
|
||||
|
@ -74,7 +75,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
|||
}
|
||||
|
||||
private:
|
||||
const dynamic_bitset<>& _values;
|
||||
const dynamic_bitset<unsigned long long, Allocator>& _values;
|
||||
const til::rectangle _rc;
|
||||
ptrdiff_t _pos;
|
||||
ptrdiff_t _nextPos;
|
||||
|
@ -130,372 +131,459 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
|||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class bitmap
|
||||
{
|
||||
public:
|
||||
using const_iterator = details::_bitmap_const_iterator;
|
||||
|
||||
bitmap() noexcept :
|
||||
_sz{},
|
||||
_rc{},
|
||||
_bits{},
|
||||
_runs{}
|
||||
template<typename Allocator = std::allocator<unsigned long long>>
|
||||
class bitmap
|
||||
{
|
||||
}
|
||||
public:
|
||||
using allocator_type = Allocator;
|
||||
using const_iterator = details::_bitmap_const_iterator<allocator_type>;
|
||||
|
||||
bitmap(til::size sz) :
|
||||
bitmap(sz, false)
|
||||
{
|
||||
}
|
||||
private:
|
||||
using run_allocator_type = typename std::allocator_traits<allocator_type>::template rebind_alloc<til::rectangle>;
|
||||
|
||||
bitmap(til::size sz, bool fill) :
|
||||
_sz(sz),
|
||||
_rc(sz),
|
||||
_bits(_sz.area()),
|
||||
_runs{}
|
||||
{
|
||||
if (fill)
|
||||
public:
|
||||
explicit bitmap(const allocator_type& allocator) noexcept :
|
||||
_alloc{ allocator },
|
||||
_sz{},
|
||||
_rc{},
|
||||
_bits{ _alloc },
|
||||
_runs{ _alloc }
|
||||
{
|
||||
set_all();
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool operator==(const bitmap& other) const noexcept
|
||||
{
|
||||
return _sz == other._sz &&
|
||||
_rc == other._rc &&
|
||||
_bits == other._bits;
|
||||
// _runs excluded because it's a cache of generated state.
|
||||
}
|
||||
|
||||
constexpr bool operator!=(const bitmap& other) const noexcept
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
const_iterator begin() const
|
||||
{
|
||||
return const_iterator(_bits, _sz, 0);
|
||||
}
|
||||
|
||||
const_iterator end() const
|
||||
{
|
||||
return const_iterator(_bits, _sz, _sz.area());
|
||||
}
|
||||
|
||||
const std::vector<til::rectangle>& runs() const
|
||||
{
|
||||
// If we don't have cached runs, rebuild.
|
||||
if (!_runs.has_value())
|
||||
{
|
||||
_runs.emplace(begin(), end());
|
||||
}
|
||||
|
||||
// Return a reference to the runs.
|
||||
return _runs.value();
|
||||
}
|
||||
|
||||
// optional fill the uncovered area with bits.
|
||||
void translate(const til::point delta, bool fill = false)
|
||||
{
|
||||
if (delta.x() == 0)
|
||||
bitmap() noexcept :
|
||||
bitmap(allocator_type{})
|
||||
{
|
||||
// fast path by using bit shifting
|
||||
translate_y(delta.y(), fill);
|
||||
return;
|
||||
}
|
||||
|
||||
// FUTURE: PERF: GH #4015: This could use in-place walk semantics instead of a temporary.
|
||||
til::bitmap other{ _sz };
|
||||
|
||||
for (auto run : *this)
|
||||
bitmap(til::size sz) :
|
||||
bitmap(sz, false, allocator_type{})
|
||||
{
|
||||
// Offset by the delta
|
||||
run += delta;
|
||||
|
||||
// Intersect with the bounds of our bitmap area
|
||||
// as part of it could have slid out of bounds.
|
||||
run &= _rc;
|
||||
|
||||
// Set it into the new bitmap.
|
||||
other.set(run);
|
||||
}
|
||||
|
||||
// If we were asked to fill... find the uncovered region.
|
||||
if (fill)
|
||||
bitmap(til::size sz, const allocator_type& allocator) :
|
||||
bitmap(sz, false, allocator)
|
||||
{
|
||||
// Original Rect of As.
|
||||
//
|
||||
// X <-- origin
|
||||
// A A A A
|
||||
// A A A A
|
||||
// A A A A
|
||||
// A A A A
|
||||
const auto originalRect = _rc;
|
||||
}
|
||||
|
||||
// If Delta = (2, 2)
|
||||
// Translated Rect of Bs.
|
||||
//
|
||||
// X <-- origin
|
||||
//
|
||||
//
|
||||
// B B B B
|
||||
// B B B B
|
||||
// B B B B
|
||||
// B B B B
|
||||
const auto translatedRect = _rc + delta;
|
||||
bitmap(til::size sz, bool fill, const allocator_type& allocator) :
|
||||
_alloc{ allocator },
|
||||
_sz(sz),
|
||||
_rc(sz),
|
||||
_bits(_sz.area(), fill ? std::numeric_limits<unsigned long long>::max() : 0, _alloc),
|
||||
_runs{ _alloc }
|
||||
{
|
||||
}
|
||||
|
||||
// Subtract the B from the A one to see what wasn't filled by the move.
|
||||
// C is the overlap of A and B:
|
||||
//
|
||||
// X <-- origin
|
||||
// A A A A 1 1 1 1
|
||||
// A A A A 1 1 1 1
|
||||
// A A C C B B subtract 2 2
|
||||
// A A C C B B ---------> 2 2
|
||||
// B B B B A - B
|
||||
// B B B B
|
||||
//
|
||||
// 1 and 2 are the spaces to fill that are "uncovered".
|
||||
const auto fillRects = originalRect - translatedRect;
|
||||
for (const auto& f : fillRects)
|
||||
bitmap(til::size sz, bool fill) :
|
||||
bitmap(sz, fill, allocator_type{})
|
||||
{
|
||||
}
|
||||
|
||||
bitmap(const bitmap& other) :
|
||||
_alloc{ std::allocator_traits<allocator_type>::select_on_container_copy_construction(other._alloc) },
|
||||
_sz{ other._sz },
|
||||
_rc{ other._rc },
|
||||
_bits{ other._bits },
|
||||
_runs{ other._runs }
|
||||
{
|
||||
// copy constructor is required to call select_on_container_copy
|
||||
}
|
||||
|
||||
bitmap& operator=(const bitmap& other)
|
||||
{
|
||||
if constexpr (std::allocator_traits<allocator_type>::propagate_on_container_copy_assignment::value)
|
||||
{
|
||||
other.set(f);
|
||||
_alloc = other._alloc;
|
||||
}
|
||||
_sz = other._sz;
|
||||
_rc = other._rc;
|
||||
_bits = other._bits;
|
||||
_runs = other._runs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Swap us with the temporary one.
|
||||
std::swap(other, *this);
|
||||
}
|
||||
|
||||
void set(const til::point pt)
|
||||
{
|
||||
THROW_HR_IF(E_INVALIDARG, !_rc.contains(pt));
|
||||
_runs.reset(); // reset cached runs on any non-const method
|
||||
|
||||
_bits.set(_rc.index_of(pt));
|
||||
}
|
||||
|
||||
void set(const til::rectangle rc)
|
||||
{
|
||||
THROW_HR_IF(E_INVALIDARG, !_rc.contains(rc));
|
||||
_runs.reset(); // reset cached runs on any non-const method
|
||||
|
||||
for (auto row = rc.top(); row < rc.bottom(); ++row)
|
||||
bitmap(bitmap&& other) noexcept :
|
||||
_alloc{ std::move(other._alloc) },
|
||||
_sz{ std::move(other._sz) },
|
||||
_rc{ std::move(other._rc) },
|
||||
_bits{ std::move(other._bits) },
|
||||
_runs{ std::move(other._runs) }
|
||||
{
|
||||
_bits.set(_rc.index_of(til::point{ rc.left(), row }), rc.width(), true);
|
||||
}
|
||||
}
|
||||
|
||||
void set_all() noexcept
|
||||
{
|
||||
_runs.reset(); // reset cached runs on any non-const method
|
||||
_bits.set();
|
||||
}
|
||||
|
||||
void reset_all() noexcept
|
||||
{
|
||||
_runs.reset(); // reset cached runs on any non-const method
|
||||
_bits.reset();
|
||||
}
|
||||
|
||||
// True if we resized. False if it was the same size as before.
|
||||
// Set fill if you want the new region (on growing) to be marked dirty.
|
||||
bool resize(til::size size, bool fill = false)
|
||||
{
|
||||
_runs.reset(); // reset cached runs on any non-const method
|
||||
|
||||
// Don't resize if it's not different
|
||||
if (_sz != size)
|
||||
bitmap& operator=(bitmap&& other) noexcept
|
||||
{
|
||||
// Make a new bitmap for the other side, empty initially.
|
||||
auto newMap = bitmap(size, false);
|
||||
|
||||
// Copy any regions that overlap from this map to the new one.
|
||||
// Just iterate our runs...
|
||||
for (const auto& run : *this)
|
||||
if constexpr (std::allocator_traits<allocator_type>::propagate_on_container_move_assignment::value)
|
||||
{
|
||||
// intersect them with the new map
|
||||
// so we don't attempt to set bits that fit outside
|
||||
// the new one.
|
||||
const auto intersect = run & newMap._rc;
|
||||
_alloc = std::move(other._alloc);
|
||||
}
|
||||
_bits = std::move(other._bits);
|
||||
_runs = std::move(other._runs);
|
||||
_sz = std::move(other._sz);
|
||||
_rc = std::move(other._rc);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// and if there is still anything left, set them.
|
||||
if (!intersect.empty())
|
||||
{
|
||||
newMap.set(intersect);
|
||||
}
|
||||
~bitmap() {}
|
||||
|
||||
void swap(bitmap& other)
|
||||
{
|
||||
if constexpr (std::allocator_traits<allocator_type>::propagate_on_container_swap::value)
|
||||
{
|
||||
std::swap(_alloc, other._alloc);
|
||||
}
|
||||
std::swap(_bits, other._bits);
|
||||
std::swap(_runs, other._runs);
|
||||
std::swap(_sz, other._sz);
|
||||
std::swap(_rc, other._rc);
|
||||
}
|
||||
|
||||
constexpr bool operator==(const bitmap& other) const noexcept
|
||||
{
|
||||
return _sz == other._sz &&
|
||||
_rc == other._rc &&
|
||||
_bits == other._bits;
|
||||
// _runs excluded because it's a cache of generated state.
|
||||
}
|
||||
|
||||
constexpr bool operator!=(const bitmap& other) const noexcept
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
const_iterator begin() const
|
||||
{
|
||||
return const_iterator(_bits, _sz, 0);
|
||||
}
|
||||
|
||||
const_iterator end() const
|
||||
{
|
||||
return const_iterator(_bits, _sz, _sz.area());
|
||||
}
|
||||
|
||||
const std::vector<til::rectangle, run_allocator_type>& runs() const
|
||||
{
|
||||
// If we don't have cached runs, rebuild.
|
||||
if (!_runs.has_value())
|
||||
{
|
||||
_runs.emplace(begin(), end());
|
||||
}
|
||||
|
||||
// Then, if we were requested to fill the new space on growing,
|
||||
// find the space in the new rectangle that wasn't in the old
|
||||
// and fill it up.
|
||||
// Return a reference to the runs.
|
||||
return _runs.value();
|
||||
}
|
||||
|
||||
// optional fill the uncovered area with bits.
|
||||
void translate(const til::point delta, bool fill = false)
|
||||
{
|
||||
if (delta.x() == 0)
|
||||
{
|
||||
// fast path by using bit shifting
|
||||
translate_y(delta.y(), fill);
|
||||
return;
|
||||
}
|
||||
|
||||
// FUTURE: PERF: GH #4015: This could use in-place walk semantics instead of a temporary.
|
||||
bitmap<allocator_type> other{ _sz, _alloc };
|
||||
|
||||
for (auto run : *this)
|
||||
{
|
||||
// Offset by the delta
|
||||
run += delta;
|
||||
|
||||
// Intersect with the bounds of our bitmap area
|
||||
// as part of it could have slid out of bounds.
|
||||
run &= _rc;
|
||||
|
||||
// Set it into the new bitmap.
|
||||
other.set(run);
|
||||
}
|
||||
|
||||
// If we were asked to fill... find the uncovered region.
|
||||
if (fill)
|
||||
{
|
||||
// A subtraction will yield anything in the new that isn't
|
||||
// a part of the old.
|
||||
const auto newAreas = newMap._rc - _rc;
|
||||
for (const auto& area : newAreas)
|
||||
// Original Rect of As.
|
||||
//
|
||||
// X <-- origin
|
||||
// A A A A
|
||||
// A A A A
|
||||
// A A A A
|
||||
// A A A A
|
||||
const auto originalRect = _rc;
|
||||
|
||||
// If Delta = (2, 2)
|
||||
// Translated Rect of Bs.
|
||||
//
|
||||
// X <-- origin
|
||||
//
|
||||
//
|
||||
// B B B B
|
||||
// B B B B
|
||||
// B B B B
|
||||
// B B B B
|
||||
const auto translatedRect = _rc + delta;
|
||||
|
||||
// Subtract the B from the A one to see what wasn't filled by the move.
|
||||
// C is the overlap of A and B:
|
||||
//
|
||||
// X <-- origin
|
||||
// A A A A 1 1 1 1
|
||||
// A A A A 1 1 1 1
|
||||
// A A C C B B subtract 2 2
|
||||
// A A C C B B ---------> 2 2
|
||||
// B B B B A - B
|
||||
// B B B B
|
||||
//
|
||||
// 1 and 2 are the spaces to fill that are "uncovered".
|
||||
const auto fillRects = originalRect - translatedRect;
|
||||
for (const auto& f : fillRects)
|
||||
{
|
||||
newMap.set(area);
|
||||
other.set(f);
|
||||
}
|
||||
}
|
||||
|
||||
// Swap and return.
|
||||
std::swap(newMap, *this);
|
||||
|
||||
return true;
|
||||
// Swap us with the temporary one.
|
||||
std::swap(other, *this);
|
||||
}
|
||||
else
|
||||
|
||||
void set(const til::point pt)
|
||||
{
|
||||
return false;
|
||||
THROW_HR_IF(E_INVALIDARG, !_rc.contains(pt));
|
||||
_runs.reset(); // reset cached runs on any non-const method
|
||||
|
||||
_bits.set(_rc.index_of(pt));
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool one() const noexcept
|
||||
{
|
||||
return _bits.count() == 1;
|
||||
}
|
||||
|
||||
constexpr bool any() const noexcept
|
||||
{
|
||||
return !none();
|
||||
}
|
||||
|
||||
constexpr bool none() const noexcept
|
||||
{
|
||||
return _bits.none();
|
||||
}
|
||||
|
||||
constexpr bool all() const noexcept
|
||||
{
|
||||
return _bits.all();
|
||||
}
|
||||
|
||||
constexpr til::size size() const noexcept
|
||||
{
|
||||
return _sz;
|
||||
}
|
||||
|
||||
std::wstring to_string() const
|
||||
{
|
||||
std::wstringstream wss;
|
||||
wss << std::endl
|
||||
<< L"Bitmap of size " << _sz.to_string() << " contains the following dirty regions:" << std::endl;
|
||||
wss << L"Runs:" << std::endl;
|
||||
|
||||
for (auto& item : *this)
|
||||
void set(const til::rectangle rc)
|
||||
{
|
||||
wss << L"\t- " << item.to_string() << std::endl;
|
||||
THROW_HR_IF(E_INVALIDARG, !_rc.contains(rc));
|
||||
_runs.reset(); // reset cached runs on any non-const method
|
||||
|
||||
for (auto row = rc.top(); row < rc.bottom(); ++row)
|
||||
{
|
||||
_bits.set(_rc.index_of(til::point{ rc.left(), row }), rc.width(), true);
|
||||
}
|
||||
}
|
||||
|
||||
return wss.str();
|
||||
}
|
||||
|
||||
private:
|
||||
void translate_y(ptrdiff_t delta_y, bool fill)
|
||||
{
|
||||
if (delta_y == 0)
|
||||
void set_all() noexcept
|
||||
{
|
||||
return;
|
||||
_runs.reset(); // reset cached runs on any non-const method
|
||||
_bits.set();
|
||||
}
|
||||
|
||||
const auto bitShift = delta_y * _sz.width();
|
||||
void reset_all() noexcept
|
||||
{
|
||||
_runs.reset(); // reset cached runs on any non-const method
|
||||
_bits.reset();
|
||||
}
|
||||
|
||||
// True if we resized. False if it was the same size as before.
|
||||
// Set fill if you want the new region (on growing) to be marked dirty.
|
||||
bool resize(til::size size, bool fill = false)
|
||||
{
|
||||
_runs.reset(); // reset cached runs on any non-const method
|
||||
|
||||
// Don't resize if it's not different
|
||||
if (_sz != size)
|
||||
{
|
||||
// Make a new bitmap for the other side, empty initially.
|
||||
bitmap<allocator_type> newMap{ size, false, _alloc };
|
||||
|
||||
// Copy any regions that overlap from this map to the new one.
|
||||
// Just iterate our runs...
|
||||
for (const auto& run : *this)
|
||||
{
|
||||
// intersect them with the new map
|
||||
// so we don't attempt to set bits that fit outside
|
||||
// the new one.
|
||||
const auto intersect = run & newMap._rc;
|
||||
|
||||
// and if there is still anything left, set them.
|
||||
if (!intersect.empty())
|
||||
{
|
||||
newMap.set(intersect);
|
||||
}
|
||||
}
|
||||
|
||||
// Then, if we were requested to fill the new space on growing,
|
||||
// find the space in the new rectangle that wasn't in the old
|
||||
// and fill it up.
|
||||
if (fill)
|
||||
{
|
||||
// A subtraction will yield anything in the new that isn't
|
||||
// a part of the old.
|
||||
const auto newAreas = newMap._rc - _rc;
|
||||
for (const auto& area : newAreas)
|
||||
{
|
||||
newMap.set(area);
|
||||
}
|
||||
}
|
||||
|
||||
// Swap and return.
|
||||
std::swap(newMap, *this);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool one() const noexcept
|
||||
{
|
||||
return _bits.count() == 1;
|
||||
}
|
||||
|
||||
constexpr bool any() const noexcept
|
||||
{
|
||||
return !none();
|
||||
}
|
||||
|
||||
constexpr bool none() const noexcept
|
||||
{
|
||||
return _bits.none();
|
||||
}
|
||||
|
||||
constexpr bool all() const noexcept
|
||||
{
|
||||
return _bits.all();
|
||||
}
|
||||
|
||||
constexpr til::size size() const noexcept
|
||||
{
|
||||
return _sz;
|
||||
}
|
||||
|
||||
std::wstring to_string() const
|
||||
{
|
||||
std::wstringstream wss;
|
||||
wss << std::endl
|
||||
<< L"Bitmap of size " << _sz.to_string() << " contains the following dirty regions:" << std::endl;
|
||||
wss << L"Runs:" << std::endl;
|
||||
|
||||
for (auto& item : *this)
|
||||
{
|
||||
wss << L"\t- " << item.to_string() << std::endl;
|
||||
}
|
||||
|
||||
return wss.str();
|
||||
}
|
||||
|
||||
private:
|
||||
void translate_y(ptrdiff_t delta_y, bool fill)
|
||||
{
|
||||
if (delta_y == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto bitShift = delta_y * _sz.width();
|
||||
|
||||
#pragma warning(push)
|
||||
// we can't depend on GSL here, so we use static_cast for explicit narrowing
|
||||
// we can't depend on GSL here, so we use static_cast for explicit narrowing
|
||||
#pragma warning(disable : 26472)
|
||||
const auto newBits = static_cast<size_t>(std::abs(bitShift));
|
||||
const auto newBits = static_cast<size_t>(std::abs(bitShift));
|
||||
#pragma warning(pop)
|
||||
const bool isLeftShift = bitShift > 0;
|
||||
const bool isLeftShift = bitShift > 0;
|
||||
|
||||
if (newBits >= _bits.size())
|
||||
{
|
||||
if (fill)
|
||||
if (newBits >= _bits.size())
|
||||
{
|
||||
set_all();
|
||||
if (fill)
|
||||
{
|
||||
set_all();
|
||||
}
|
||||
else
|
||||
{
|
||||
reset_all();
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
reset_all();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLeftShift)
|
||||
{
|
||||
// This operator doesn't modify the size of `_bits`: the
|
||||
// new bits are set to 0.
|
||||
_bits <<= newBits;
|
||||
}
|
||||
else
|
||||
{
|
||||
_bits >>= newBits;
|
||||
}
|
||||
|
||||
if (fill)
|
||||
{
|
||||
if (isLeftShift)
|
||||
{
|
||||
_bits.set(0, newBits, true);
|
||||
// This operator doesn't modify the size of `_bits`: the
|
||||
// new bits are set to 0.
|
||||
_bits <<= newBits;
|
||||
}
|
||||
else
|
||||
{
|
||||
_bits.set(_bits.size() - newBits, newBits, true);
|
||||
_bits >>= newBits;
|
||||
}
|
||||
|
||||
if (fill)
|
||||
{
|
||||
if (isLeftShift)
|
||||
{
|
||||
_bits.set(0, newBits, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_bits.set(_bits.size() - newBits, newBits, true);
|
||||
}
|
||||
}
|
||||
|
||||
_runs.reset(); // reset cached runs on any non-const method
|
||||
}
|
||||
|
||||
_runs.reset(); // reset cached runs on any non-const method
|
||||
}
|
||||
allocator_type _alloc;
|
||||
til::size _sz;
|
||||
til::rectangle _rc;
|
||||
dynamic_bitset<unsigned long long, allocator_type> _bits;
|
||||
|
||||
til::size _sz;
|
||||
til::rectangle _rc;
|
||||
dynamic_bitset<> _bits;
|
||||
|
||||
mutable std::optional<std::vector<til::rectangle>> _runs;
|
||||
mutable std::optional<std::vector<til::rectangle, run_allocator_type>> _runs;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class ::BitmapTests;
|
||||
friend class ::BitmapTests;
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
using bitmap = ::til::details::bitmap<>;
|
||||
|
||||
namespace pmr
|
||||
{
|
||||
using bitmap = ::til::details::bitmap<std::pmr::polymorphic_allocator<unsigned long long>>;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __WEX_COMMON_H__
|
||||
namespace WEX::TestExecution
|
||||
{
|
||||
template<>
|
||||
class VerifyOutputTraits<::til::bitmap>
|
||||
template<typename T>
|
||||
class VerifyOutputTraits<::til::details::bitmap<T>>
|
||||
{
|
||||
public:
|
||||
static WEX::Common::NoThrowString ToString(const ::til::bitmap& rect)
|
||||
static WEX::Common::NoThrowString ToString(const ::til::details::bitmap<T>& rect)
|
||||
{
|
||||
return WEX::Common::NoThrowString(rect.to_string().c_str());
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
class VerifyCompareTraits<::til::bitmap, ::til::bitmap>
|
||||
template<typename T>
|
||||
class VerifyCompareTraits<::til::details::bitmap<T>, ::til::details::bitmap<T>>
|
||||
{
|
||||
public:
|
||||
static bool AreEqual(const ::til::bitmap& expected, const ::til::bitmap& actual) noexcept
|
||||
static bool AreEqual(const ::til::details::bitmap<T>& expected, const ::til::details::bitmap<T>& actual) noexcept
|
||||
{
|
||||
return expected == actual;
|
||||
}
|
||||
|
||||
static bool AreSame(const ::til::bitmap& expected, const ::til::bitmap& actual) noexcept
|
||||
static bool AreSame(const ::til::details::bitmap<T>& expected, const ::til::details::bitmap<T>& actual) noexcept
|
||||
{
|
||||
return &expected == &actual;
|
||||
}
|
||||
|
||||
static bool IsLessThan(const ::til::bitmap& expectedLess, const ::til::bitmap& expectedGreater) = delete;
|
||||
static bool IsLessThan(const ::til::details::bitmap<T>& expectedLess, const ::til::details::bitmap<T>& expectedGreater) = delete;
|
||||
|
||||
static bool IsGreaterThan(const ::til::bitmap& expectedGreater, const ::til::bitmap& expectedLess) = delete;
|
||||
static bool IsGreaterThan(const ::til::details::bitmap<T>& expectedGreater, const ::til::details::bitmap<T>& expectedLess) = delete;
|
||||
|
||||
static bool IsNull(const ::til::bitmap& object) noexcept
|
||||
static bool IsNull(const ::til::details::bitmap<T>& object) noexcept
|
||||
{
|
||||
return object == til::bitmap{};
|
||||
return object == til::details::bitmap<T>{};
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -13,14 +13,16 @@ class BitmapTests
|
|||
{
|
||||
TEST_CLASS(BitmapTests);
|
||||
|
||||
template<typename T>
|
||||
void _checkBits(const til::rectangle& bitsOn,
|
||||
const til::bitmap& map)
|
||||
const til::details::bitmap<T>& map)
|
||||
{
|
||||
_checkBits(std::vector<til::rectangle>{ bitsOn }, map);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void _checkBits(const std::vector<til::rectangle>& bitsOn,
|
||||
const til::bitmap& map)
|
||||
const til::details::bitmap<T>& map)
|
||||
{
|
||||
Log::Comment(L"Check all bits in map.");
|
||||
// For every point in the map...
|
||||
|
@ -838,7 +840,145 @@ class BitmapTests
|
|||
// 0 1 1 0 _ F F _
|
||||
Log::Comment(L"Set up a bitmap with some runs.");
|
||||
|
||||
til::bitmap map{ til::size{ 4, 4 } };
|
||||
til::bitmap map{ til::size{ 4, 4 }, false };
|
||||
|
||||
// 0 0 0 0 |1 1|0 0
|
||||
// 0 0 0 0 0 0 0 0
|
||||
// 0 0 0 0 --> 0 0 0 0
|
||||
// 0 0 0 0 0 0 0 0
|
||||
map.set(til::rectangle{ til::point{ 0, 0 }, til::size{ 2, 1 } });
|
||||
|
||||
// 1 1 0 0 1 1 0 0
|
||||
// 0 0 0 0 0 0|1|0
|
||||
// 0 0 0 0 --> 0 0|1|0
|
||||
// 0 0 0 0 0 0|1|0
|
||||
map.set(til::rectangle{ til::point{ 2, 1 }, til::size{ 1, 3 } });
|
||||
|
||||
// 1 1 0 0 1 1 0|1|
|
||||
// 0 0 1 0 0 0 1|1|
|
||||
// 0 0 1 0 --> 0 0 1 0
|
||||
// 0 0 1 0 0 0 1 0
|
||||
map.set(til::rectangle{ til::point{ 3, 0 }, til::size{ 1, 2 } });
|
||||
|
||||
// 1 1 0 1 1 1 0 1
|
||||
// 0 0 1 1 |1|0 1 1
|
||||
// 0 0 1 0 --> 0 0 1 0
|
||||
// 0 0 1 0 0 0 1 0
|
||||
map.set(til::point{ 0, 1 });
|
||||
|
||||
// 1 1 0 1 1 1 0 1
|
||||
// 1 0 1 1 1 0 1 1
|
||||
// 0 0 1 0 --> 0 0 1 0
|
||||
// 0 0 1 0 0|1|1 0
|
||||
map.set(til::point{ 1, 3 });
|
||||
|
||||
Log::Comment(L"Building the expected run rectangles.");
|
||||
|
||||
// Reminder, we're making 6 rectangle runs A-F like this:
|
||||
// A A _ B
|
||||
// C _ D D
|
||||
// _ _ E _
|
||||
// _ F F _
|
||||
til::some<til::rectangle, 6> expected;
|
||||
expected.push_back(til::rectangle{ til::point{ 0, 0 }, til::size{ 2, 1 } });
|
||||
expected.push_back(til::rectangle{ til::point{ 3, 0 }, til::size{ 1, 1 } });
|
||||
expected.push_back(til::rectangle{ til::point{ 0, 1 }, til::size{ 1, 1 } });
|
||||
expected.push_back(til::rectangle{ til::point{ 2, 1 }, til::size{ 2, 1 } });
|
||||
expected.push_back(til::rectangle{ til::point{ 2, 2 }, til::size{ 1, 1 } });
|
||||
expected.push_back(til::rectangle{ til::point{ 1, 3 }, til::size{ 2, 1 } });
|
||||
|
||||
Log::Comment(L"Run the iterator and collect the runs.");
|
||||
til::some<til::rectangle, 6> actual;
|
||||
for (auto run : map.runs())
|
||||
{
|
||||
actual.push_back(run);
|
||||
}
|
||||
|
||||
Log::Comment(L"Verify they match what we expected.");
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
Log::Comment(L"Clear the map and iterate and make sure we get no results.");
|
||||
map.reset_all();
|
||||
|
||||
expected.clear();
|
||||
actual.clear();
|
||||
for (auto run : map.runs())
|
||||
{
|
||||
actual.push_back(run);
|
||||
}
|
||||
|
||||
Log::Comment(L"Verify they're empty.");
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
Log::Comment(L"Set point and validate runs updated.");
|
||||
const til::point setPoint{ 2, 2 };
|
||||
expected.push_back(til::rectangle{ setPoint });
|
||||
map.set(setPoint);
|
||||
|
||||
for (auto run : map.runs())
|
||||
{
|
||||
actual.push_back(run);
|
||||
}
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
Log::Comment(L"Set rectangle and validate runs updated.");
|
||||
const til::rectangle setRect{ setPoint, til::size{ 2, 2 } };
|
||||
expected.clear();
|
||||
expected.push_back(til::rectangle{ til::point{ 2, 2 }, til::size{ 2, 1 } });
|
||||
expected.push_back(til::rectangle{ til::point{ 2, 3 }, til::size{ 2, 1 } });
|
||||
map.set(setRect);
|
||||
|
||||
actual.clear();
|
||||
for (auto run : map.runs())
|
||||
{
|
||||
actual.push_back(run);
|
||||
}
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
Log::Comment(L"Set all and validate runs updated.");
|
||||
expected.clear();
|
||||
expected.push_back(til::rectangle{ til::point{ 0, 0 }, til::size{ 4, 1 } });
|
||||
expected.push_back(til::rectangle{ til::point{ 0, 1 }, til::size{ 4, 1 } });
|
||||
expected.push_back(til::rectangle{ til::point{ 0, 2 }, til::size{ 4, 1 } });
|
||||
expected.push_back(til::rectangle{ til::point{ 0, 3 }, til::size{ 4, 1 } });
|
||||
map.set_all();
|
||||
|
||||
actual.clear();
|
||||
for (auto run : map.runs())
|
||||
{
|
||||
actual.push_back(run);
|
||||
}
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
Log::Comment(L"Resize and validate runs updated.");
|
||||
const til::size newSize{ 3, 3 };
|
||||
expected.clear();
|
||||
expected.push_back(til::rectangle{ til::point{ 0, 0 }, til::size{ 3, 1 } });
|
||||
expected.push_back(til::rectangle{ til::point{ 0, 1 }, til::size{ 3, 1 } });
|
||||
expected.push_back(til::rectangle{ til::point{ 0, 2 }, til::size{ 3, 1 } });
|
||||
map.resize(newSize);
|
||||
|
||||
actual.clear();
|
||||
for (auto run : map.runs())
|
||||
{
|
||||
actual.push_back(run);
|
||||
}
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(RunsWithPmr)
|
||||
{
|
||||
// This is a copy of the above test, but with a pmr::bitmap.
|
||||
std::pmr::unsynchronized_pool_resource pool;
|
||||
|
||||
// This map --> Those runs
|
||||
// 1 1 0 1 A A _ B
|
||||
// 1 0 1 1 C _ D D
|
||||
// 0 0 1 0 _ _ E _
|
||||
// 0 1 1 0 _ F F _
|
||||
Log::Comment(L"Set up a PMR bitmap with some runs.");
|
||||
|
||||
til::pmr::bitmap map{ til::size{ 4, 4 }, false, &pool };
|
||||
|
||||
// 0 0 0 0 |1 1|0 0
|
||||
// 0 0 0 0 0 0 0 0
|
||||
|
|
Loading…
Reference in a new issue