diff --git a/include/bx/bitarray.h b/include/bx/bitarray.h new file mode 100644 index 0000000..f3e5a1e --- /dev/null +++ b/include/bx/bitarray.h @@ -0,0 +1,562 @@ +/* + * Copyright 2010-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bx#license-bsd-2-clause + */ + +#ifndef BX_BITARRAY_H_HEADER_GUARD +#define BX_BITARRAY_H_HEADER_GUARD + +#include +#include +#include + +namespace bx +{ + /// Base class for immutable bit array views providing read-only access to a contiguous bit set. + class BitArrayViewBase + { + public: + /// Returns the number of 64-bit words needed for the given number of bits. + /// + /// @param[in] _numBits Number of bits. + /// + static constexpr uint32_t getNumWords(uint32_t _numBits) { return alignUp(_numBits, 64)/64; } + + /// Default constructor. Constructs an empty view. + BitArrayViewBase(); + + /// Constructs a bit array view from existing memory. + /// + /// @param[in] _ptr Pointer to existing memory. + /// @param[in] _sizeInBytes Number of bytes, must be multiple of 8-bytes. + /// @param[in] _count Number of bits. + /// + BitArrayViewBase(void* _ptr, uint32_t _sizeInBytes, uint32_t _count = UINT32_MAX); + + /// Returns the value of a single bit. + /// + /// @param[in] _idx Bit index. + /// + /// @returns Bit value. + /// + bool get(uint32_t _idx) const; + + /// Returns bits in the range [_beginBit, _endBit) packed into a uint64_t. + /// + /// @param[in] _beginBit First bit index (inclusive). + /// @param[in] _endBit Last bit index (exclusive). + /// + /// @returns Packed bit values. + /// + uint64_t get(uint32_t _beginBit, uint32_t _endBit) const; + + /// Returns a const pointer to the underlying memory. + const void* getPtr() const; + + /// Returns a const pointer past the end of the underlying memory. + const void* getTerm() const; + + /// Returns the number of bits in the bit set. + uint32_t getCount() const; + + /// Returns the maximum number of bits the bit set can hold. + uint32_t getCapacity() const; + + /// Finds a contiguous run of zero bits. + /// + /// @param[in] _numBits Number of contiguous zero bits to find. + /// + /// @returns Index of the first bit in the run, or kInvalid if not found. + /// + uint32_t find(uint32_t _numBits) const; + + /// Finds a contiguous run of zero bits within a range. + /// + /// @param[in] _beginBit First bit index (inclusive). + /// @param[in] _endBit Last bit index (exclusive). + /// @param[in] _numBits Number of contiguous zero bits to find. + /// + /// @returns Index of the first bit in the run, or kInvalid if not found. + /// + uint32_t find(uint32_t _beginBit, uint32_t _endBit, uint32_t _numBits) const; + + /// Finds the first or last set bit. + /// + /// @param[in] _element kFirst or kLast. + /// + /// @returns Index of the found set bit, or kInvalid. + /// + uint32_t findSet(Element _element) const; + + /// Finds the first set bit in the range [_beginBit, _endBit). + /// + /// @param[in] _beginBit First bit index (inclusive). + /// @param[in] _endBit Last bit index (exclusive). + /// + /// @returns Index of the found set bit, or kInvalid. + /// + uint32_t findSet(uint32_t _beginBit, uint32_t _endBit) const; + + /// Finds the first clear bit in the range [_beginBit, _endBit). + /// + /// @param[in] _beginBit First bit index (inclusive). + /// @param[in] _endBit Last bit index (exclusive). + /// + /// @returns Index of the found clear bit, or kInvalid. + /// + uint32_t findClear(uint32_t _beginBit, uint32_t _endBit) const; + + /// Returns the total number of set bits. + uint32_t countBits() const; + + /// Returns the number of set bits in the range [_beginBit, _endBit). + /// + /// @param[in] _beginBit First bit index (inclusive). + /// @param[in] _endBit Last bit index (exclusive). + /// + /// @returns Number of set bits in the range. + /// + uint32_t countBits(uint32_t _beginBit, uint32_t _endBit) const; + + /// Returns true if any bit is set. + bool testAny() const; + + /// Returns true if any bit in the range [_beginBit, _endBit) is set. + /// + /// @param[in] _beginBit First bit index (inclusive). + /// @param[in] _endBit Last bit index (exclusive). + /// + bool testAny(uint32_t _beginBit, uint32_t _endBit) const; + + /// Returns true if all bits are set. + bool testAll() const; + + /// Returns true if all bits in the range [_beginBit, _endBit) are set. + /// + /// @param[in] _beginBit First bit index (inclusive). + /// @param[in] _endBit Last bit index (exclusive). + /// + bool testAll(uint32_t _beginBit, uint32_t _endBit) const; + + /// Returns true if no bits are set. + bool testNone() const; + + /// Returns true if no bits in the range [_beginBit, _endBit) are set. + /// + /// @param[in] _beginBit First bit index (inclusive). + /// @param[in] _endBit Last bit index (exclusive). + /// + bool testNone(uint32_t _beginBit, uint32_t _endBit) const; + + protected: + /// `find` without argument validation. `find` is inline, so that incorrect + /// usage asserts in caller's code. + uint32_t findRange(uint32_t _beginBit, uint32_t _endBit, uint32_t _numBits) const; + + uint64_t* m_ptr; + uint32_t m_sizeInBytes; + uint32_t m_count; + }; + + /// Mutable, non-owning view into an existing bit array. + class MutableBitArrayView : public BitArrayViewBase + { + public: + /// Default constructor. Constructs an empty mutable view. + MutableBitArrayView(); + + /// Constructs a mutable bit array view from existing memory. + /// + /// @param[in] _ptr Pointer to existing memory. + /// @param[in] _sizeInBytes Number of bytes, must be multiple of 8-bytes. + /// @param[in] _count Number of bits. + /// + MutableBitArrayView(void* _ptr, uint32_t _sizeInBytes, uint32_t _count = UINT32_MAX); + + /// Sets the view to point to the given memory and count. + /// + /// @param[in] _src Pointer to existing memory. + /// @param[in] _count Number of bits. + /// + void set(void* _src, uint32_t _count); + + /// Sets a single bit to the specified value. + /// + /// @param[in] _idx Bit index. + /// @param[in] _value Bit value. + /// + void set(uint32_t _idx, bool _value); + + /// Sets all bits in the range [_beginBit, _endBit) to the specified value. + /// + /// @param[in] _beginBit First bit index (inclusive). + /// @param[in] _endBit Last bit index (exclusive). + /// @param[in] _value Bit value. + /// + void set(uint32_t _beginBit, uint32_t _endBit, bool _value); + + /// Sets bits in the range [_beginBit, _endBit) from a packed uint64_t value. + /// + /// @param[in] _beginBit First bit index (inclusive). + /// @param[in] _endBit Last bit index (exclusive). + /// @param[in] _value Packed bit values. + /// + void set(uint32_t _beginBit, uint32_t _endBit, uint64_t _value); + + /// Copies bits within the same bit array. + /// + /// @param[in] _dstBit Destination bit index. + /// @param[in] _srcBit Source bit index. + /// @param[in] _numBits Number of bits to copy. + /// + void blit(uint32_t _dstBit, uint32_t _srcBit, uint32_t _numBits); + + /// Clears the bit set. + /// + /// @param[in] _all If true, clears the entire buffer. If false, clears only the counted bits. + /// + void clear(bool _all = false); + + /// Returns a mutable pointer to the underlying memory. + void* getPtr(); + + /// Returns a const pointer to the underlying memory. + const void* getPtr() const; + + /// Returns a mutable pointer past the end of the underlying memory. + void* getTerm(); + + /// Returns a const pointer past the end of the underlying memory. + const void* getTerm() const; + }; + + /// Immutable, non-owning view into an existing bit array. + class BitArrayView : public BitArrayViewBase + { + public: + }; + + /// Base class for owning bit array containers with contiguous storage. + /// + /// @tparam StorageT Storage backend type (fixed or dynamic). + /// + template + class BitArrayBaseT : public MutableBitArrayView + { + public: + /// Default constructor. Constructs an empty bit array. + BitArrayBaseT(); + + /// Destructor. Releases storage. + ~BitArrayBaseT(); + + /// Constructs a bit array by copying from a bit array view. + /// + /// @param[in] _src Source bit array view to copy from. + /// + BitArrayBaseT(const BitArrayViewBase& _src); + + /// Sets the bit array contents from a bit array view. + /// + /// @param[in] _src Source bit array view to copy from. + /// + void set(const BitArrayViewBase& _src); + + /// Sets a single bit to the specified value. + /// + /// @param[in] _idx Bit index. + /// @param[in] _value Bit value. + /// + void set(uint32_t _idx, bool _value); + + /// Sets all bits in the range [_beginBit, _endBit) to the specified value. + /// + /// @param[in] _beginBit First bit index (inclusive). + /// @param[in] _endBit Last bit index (exclusive). + /// @param[in] _value Bit value. + /// + void set(uint32_t _beginBit, uint32_t _endBit, bool _value); + + /// Sets bits in the range [_beginBit, _endBit) from a packed uint64_t value. + /// + /// @param[in] _beginBit First bit index (inclusive). + /// @param[in] _endBit Last bit index (exclusive). + /// @param[in] _value Packed bit values. + /// + void set(uint32_t _beginBit, uint32_t _endBit, uint64_t _value); + + /// Copy assignment operator from another bit array. + /// + /// @param[in] _rhs Source bit array to copy from. + /// + template + BitArrayBaseT& operator=(const BitArrayBaseT& _rhs); + + /// Returns the maximum number of bits the bit array can hold. + uint32_t getCapacity() const; + + /// Sets the storage capacity in bits. May reallocate if needed. + /// + /// @param[in] _capacityInBits Desired minimum capacity in bits. + /// + void setCapacity(uint32_t _capacityInBits); + + /// Sets the number of bits. Constructs or destroys bits as needed. + /// + /// @param[in] _countInBits Desired bit count. + /// + void setCount(uint32_t _countInBits); + + protected: + StorageT m_storage; + }; + + /// Fixed-capacity bit array type alias. + /// + /// @tparam CapacityT Capacity in bits. + /// + template + using FixedBitArrayT = BitArrayBaseT >; + + /// Dynamic bit array with heap-allocated memory managed by an allocator. + class BitArray final : public BitArrayBaseT> + { + public: + /// Default constructor is deleted; an allocator must be provided. + BitArray() = delete; + + /// Copy constructor. + /// + /// @param[in] _other Source bit array to copy from. + /// + BitArray(const BitArray& _other); + + /// Constructs an empty bit array with the specified allocator. + /// + /// @param[in] _allocator Memory allocator. + /// + BitArray(AllocatorI* _allocator); + + /// Constructs a bit array by copying from a bit array view. + /// + /// @param[in] _allocator Memory allocator. + /// @param[in] _src Source bit array view to copy from. + /// + BitArray(AllocatorI* _allocator, const BitArrayViewBase& _src); + + /// Returns the allocator used by this bit array. + AllocatorI* getAllocator() const; + + /// Copy assignment operator. + /// + /// @param[in] _other Source bit array to copy from. + /// + BitArray& operator=(const BitArray& _other); + + /// Move assignment operator. Takes ownership of the source bit array's storage. + /// + /// @param[in] _other Source bit array to move from. + /// + BitArray& operator=(BitArray&& _other); + + /// Sets the bit array contents from a bit array view. + /// + /// @param[in] _src Source bit array view to copy from. + /// + void set(const BitArrayViewBase& _src); + + /// Sets the bit array contents by moving from another bit array. + /// + /// @param[in] _other Source bit array to move from. + /// + void set(BitArray&& _other); + + /// Sets a single bit to the specified value. + /// + /// @param[in] _idx Bit index. + /// @param[in] _value Bit value. + /// + void set(uint32_t _idx, bool _value); + + /// Sets all bits in the range [_beginBit, _endBit) to the specified value. + /// + /// @param[in] _beginBit First bit index (inclusive). + /// @param[in] _endBit Last bit index (exclusive). + /// @param[in] _value Bit value. + /// + void set(uint32_t _beginBit, uint32_t _endBit, bool _value); + + /// Sets bits in the range [_beginBit, _endBit) from a packed uint64_t value. + /// + /// @param[in] _beginBit First bit index (inclusive). + /// @param[in] _endBit Last bit index (exclusive). + /// @param[in] _value Packed bit values. + /// + void set(uint32_t _beginBit, uint32_t _endBit, uint64_t _value); + + private: + /// Releases storage and resets the view to empty, so that no member + /// refers to the freed block. + /// + void release(); + }; + + /// Owning bit array with a mip of full words stored after the words in the same block. + /// Every mutator keeps the mip in step, so the first clear bit is found without scanning words. + class MippedBitArrayBase : public BitArrayViewBase + { + public: + /// Returns the number of mip words needed for the given number of words. + /// + /// @param[in] _numWords Number of 64-bit words. + /// + static constexpr uint32_t getNumMipWords(uint32_t _numWords) { return alignUp(_numWords, 64)/64; } + + /// Sets a single bit to the specified value. + /// + /// @param[in] _idx Bit index. + /// @param[in] _value Bit value. + /// + void set(uint32_t _idx, bool _value); + + /// Sets all bits in the range [_beginBit, _endBit) to the specified value. + /// + /// @param[in] _beginBit First bit index (inclusive). + /// @param[in] _endBit Last bit index (exclusive). + /// @param[in] _value Bit value. + /// + void set(uint32_t _beginBit, uint32_t _endBit, bool _value); + + /// Sets bits in the range [_beginBit, _endBit) from a packed uint64_t value. + /// + /// @param[in] _beginBit First bit index (inclusive). + /// @param[in] _endBit Last bit index (exclusive). + /// @param[in] _value Packed bit values. + /// + void set(uint32_t _beginBit, uint32_t _endBit, uint64_t _value); + + /// Copies bits within the same bit array. + /// + /// @param[in] _dstBit Destination bit index. + /// @param[in] _srcBit Source bit index. + /// @param[in] _numBits Number of bits to copy. + /// + void blit(uint32_t _dstBit, uint32_t _srcBit, uint32_t _numBits); + + /// Clears the counted bits. + void clear(); + + /// Finds the first clear bit in the range [_beginBit, _endBit) through the mip. + /// + /// @param[in] _beginBit First bit index (inclusive). + /// @param[in] _endBit Last bit index (exclusive). + /// + /// @returns Index of the found clear bit, or kInvalid. + /// + uint32_t findClear(uint32_t _beginBit, uint32_t _endBit) const; + + /// Returns a const pointer to the mip, one bit per word, set when the word is full. + const uint64_t* getMip() const; + + protected: + /// Default constructor. Constructs an empty bit array. + MippedBitArrayBase(); + + /// Binds to `_numWords` words followed by their mip, and zeroes the mip. + /// + /// @param[in] _ptr Pointer to the words. + /// @param[in] _numWords Number of 64-bit words. + /// + void rebind(uint64_t* _ptr, uint32_t _numWords); + + /// Moves the mip after the number of words changed, without rebuilding it. + /// + /// @param[in] _ptr Pointer to the words. + /// @param[in] _oldNumWords Previous number of 64-bit words. + /// @param[in] _numWords New number of 64-bit words. + /// + void moveMip(uint64_t* _ptr, uint32_t _oldNumWords, uint32_t _numWords); + + /// Recomputes the mip bits for words [_beginWord, _endWord). + /// + /// @param[in] _beginWord First word index (inclusive). + /// @param[in] _endWord Last word index (exclusive). + /// + void updateMip(uint32_t _beginWord, uint32_t _endWord); + + /// Marks the mip bits past the last word as full, so searches stop there. + void setMipTail(); + + uint64_t* m_mip; + }; + + /// Mipped bit array with contiguous storage for the words and their mip. + /// + /// @tparam StorageT Storage backend type (fixed or dynamic). + /// + template + class MippedBitArrayT : public MippedBitArrayBase + { + public: + /// Default constructor. Constructs an empty bit array. + MippedBitArrayT(); + + /// Returns the maximum number of bits the bit array can hold. + uint32_t getCapacity() const; + + /// Sets the storage capacity in bits. May reallocate, and moves the mip if it does. + /// + /// @param[in] _capacityInBits Desired capacity in bits. + /// + void setCapacity(uint32_t _capacityInBits); + + /// Sets the number of bits. New bits are cleared. + /// + /// @param[in] _countInBits Desired bit count. + /// + void setCount(uint32_t _countInBits); + + protected: + StorageT m_storage; + uint32_t m_numWords; + }; + + /// Fixed-capacity mipped bit array type alias. + /// + /// @tparam CapacityT Capacity in bits. + /// + template + using FixedMippedBitArrayT = MippedBitArrayT >; + + /// Dynamic mipped bit array with heap-allocated memory managed by an allocator. + class MippedBitArray final : public MippedBitArrayT> + { + public: + /// Default constructor is deleted; an allocator must be provided. + MippedBitArray() = delete; + + /// Copying is not supported. + MippedBitArray(const MippedBitArray&) = delete; + + /// Copying is not supported. + MippedBitArray& operator=(const MippedBitArray&) = delete; + + /// Constructs an empty bit array with the specified allocator. + /// + /// @param[in] _allocator Memory allocator. + /// + MippedBitArray(AllocatorI* _allocator); + + /// Returns the allocator used by this bit array. + AllocatorI* getAllocator() const; + }; + + +} // namespace bx + +#include "inline/bitarray.inl" + +#endif // BX_BITARRAY_H_HEADER_GUARD diff --git a/include/bx/bufferstorage.h b/include/bx/bufferstorage.h new file mode 100644 index 0000000..589b01d --- /dev/null +++ b/include/bx/bufferstorage.h @@ -0,0 +1,214 @@ +/* + * Copyright 2010-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bx#license-bsd-2-clause + */ + +#ifndef BX_BUFFERSTORAGE_H_HEADER_GUARD +#define BX_BUFFERSTORAGE_H_HEADER_GUARD + +#include +#include + +namespace bx +{ + /// Element enum. + enum class Element { First, Last }; + constexpr Element kFirst = Element::First; + constexpr Element kLast = Element::Last; + + /// Oredering enum. + enum class Ordering { Ordered, Unordered }; + constexpr Ordering kUnordered = Ordering::Unordered; + constexpr Ordering kOrdered = Ordering::Ordered; + + /// Invalid sentinel. + constexpr uint32_t kInvalid = UINT32_MAX; + + template + struct StorageViewT final + { + const Ty* m_ptr; + uint32_t m_count; + + /// + StorageViewT() = delete; + + /// + StorageViewT(const Ty* _ptr, uint32_t _count); + + /// + StorageViewT(const StorageViewT& _other); + + /// + ~StorageViewT(); + + /// + StorageViewT& operator=(const StorageViewT& _other); + + /// + StorageViewT& operator=(StorageViewT&& _other); + + /// + template + void setCapacity(StorageT& _storage, uint32_t _capacity); + + /// + [[nodiscard]] uint32_t getCount() const; + + /// + template + void setCount(StorageT& _storage, uint32_t _count); + + /// + template + void add(StorageT& _storage, uint32_t _pos, const Ty* _ptr, uint32_t _count); + + /// + template + void add(StorageT& _storage, uint32_t _pos, EmplaceTag, ArgsT&&... _args); + + /// + template + void remove(StorageT& _storage, uint32_t _pos, Ordering _ordering, uint32_t _count); + + /// + template + [[nodiscard]] uint32_t carve(StorageT& _storage, uint32_t _pos, uint32_t _count, bool _init); + }; + + /// + class StorageTraits final + { + public: + /// + template + [[nodiscard]] static uint32_t copy(Ty* _dst, const Ty* _src, uint32_t _count); + + /// + template + static void move(Ty* _dst, Ty* _src, uint32_t _count); + + /// + template + static EnableIfType::value + && !IsConstructibleT::value + , void> + constructDefault(Ty* _ptr, uint32_t _count); + + /// + template + static EnableIfType::value + && IsConstructibleT::value + , void> + constructDefault(Ty* _ptr, uint32_t _count); + + /// + template + static EnableIfType::value + , void> + constructDefault(Ty* _ptr, uint32_t _count); + + /// + template + [[nodiscard]] static uint32_t constructEmplace(Ty* _ptr, uint32_t _count, ArgsT&&... _args); + + /// + template + static void constructMove(Ty* _dst, Ty* _src, uint32_t _count); + + /// + template + static void destruct(Ty* _ptr, uint32_t _count); + }; + + /// + template + class FixedStorageT final + { + public: + /// + FixedStorageT(); + + /// + ~FixedStorageT(); + + /// + constexpr uint32_t getCapacity() const; + + /// + void setCapacity(uint32_t _capacity, uint32_t _count); + + /// + Ty* getPtr(uint32_t _idx = 0); + + /// + const Ty* getPtr(uint32_t _idx = 0) const; + + /// + Ty* getTerm(); + + /// + const Ty* getTerm() const; + + /// + FixedStorageT& operator=(const FixedStorageT& _other); + + /// + FixedStorageT& operator=(FixedStorageT&& _other); + + private: + BX_ALIGN_DECL(alignof(Ty), uint8_t) m_data[sizeof(Ty)*CapacityT]; + }; + + /// + template + class DynamicStorageT final + { + public: + /// + DynamicStorageT(); + + /// + ~DynamicStorageT(); + + /// + void setAllocator(AllocatorI* _allocator); + + /// + AllocatorI* getAllocator() const; + + /// + uint32_t getCapacity() const; + + /// + void setCapacity(uint32_t _capacity, uint32_t _count); + + /// + Ty* getPtr(uint32_t _idx = 0); + + /// + const Ty* getPtr(uint32_t _idx = 0) const; + + /// + Ty* getTerm(); + + /// + const Ty* getTerm() const; + + /// + DynamicStorageT& operator=(DynamicStorageT&& _other); + + private: + Ty* m_ptr; + uint32_t m_capacity; + AllocatorI* m_allocator; + }; + +} // namespace bx + +#include "inline/bufferstorage.inl" + +#endif // BX_BUFFERSTORAGE_H_HEADER_GUARD diff --git a/include/bx/bx.h b/include/bx/bx.h index 6b7fa8e..fa2935d 100644 --- a/include/bx/bx.h +++ b/include/bx/bx.h @@ -54,6 +54,12 @@ namespace bx /// Fields are initialized to identity value. BX_DECLARE_TAG(InitIdentity); + /// Construct in place from arguments. + BX_DECLARE_TAG(Emplace); + + /// Reinterpret data. + BX_DECLARE_TAG(Reinterpret); + /// Source location with file path, and file line. /// struct Location diff --git a/include/bx/handlealloc.h b/include/bx/handlealloc.h index 1ab6b46..3bbdc68 100644 --- a/include/bx/handlealloc.h +++ b/include/bx/handlealloc.h @@ -9,6 +9,7 @@ #include "bx.h" #include "allocator.h" #include "simd_t.h" +#include "bitarray.h" namespace bx { @@ -315,6 +316,73 @@ namespace bx HandleAllocT m_alloc; }; + /// Handle allocator backed by a bit set in caller-provided storage. + /// + class HandleAlloc2 + { + public: + /// `_bits` must hold at least `_maxHandles` bits; the allocator starts empty. + /// + HandleAlloc2(MippedBitArrayBase& _bits, uint16_t _maxHandles); + + /// + uint16_t getNumHandles() const; + + /// + uint16_t getMaxHandles() const; + + /// Allocates the lowest free handle, or returns `kInvalidHandle`. + /// + uint16_t alloc(); + + /// + bool isValid(uint16_t _handle) const; + + /// + void free(uint16_t _handle); + + /// + void reset(); + + /// Returns the lowest allocated handle, or `kInvalidHandle`. + /// + uint16_t findFirst() const; + + /// Returns the lowest allocated handle above `_handle`, or `kInvalidHandle`. + /// + uint16_t findNext(uint16_t _handle) const; + + /// + const BitArrayViewBase& getBits() const; + + protected: + /// + HandleAlloc2(InitNoneTag, MippedBitArrayBase& _bits, uint16_t _maxHandles); + + private: + HandleAlloc2(); + + uint16_t findFrom(uint32_t _start) const; + + MippedBitArrayBase& m_bits; + uint16_t m_maxHandles; + uint16_t m_numHandles; + }; + + /// Bit set handle allocator with embedded storage. + /// + template + class HandleAlloc2T : public HandleAlloc2 + { + public: + /// + HandleAlloc2T(); + + private: + FixedMippedBitArrayT m_storage; + }; + + } // namespace bx #include "inline/handlealloc.inl" diff --git a/include/bx/inline/bitarray.inl b/include/bx/inline/bitarray.inl new file mode 100644 index 0000000..a7eaf2d --- /dev/null +++ b/include/bx/inline/bitarray.inl @@ -0,0 +1,706 @@ +/* + * Copyright 2010-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bx#license-bsd-2-clause + */ + +#ifndef BX_BITARRAY_H_HEADER_GUARD +# error "Must be included from bx/bitarray.h!" +#endif // BX_BITARRAY_H_HEADER_GUARD + +namespace bx +{ + inline BitArrayViewBase::BitArrayViewBase() + : m_ptr(NULL) + , m_sizeInBytes(0) + , m_count(0) + { + } + + inline BitArrayViewBase::BitArrayViewBase(void* _ptr, uint32_t _sizeInBytes, uint32_t _count) + { + BX_ASSERT(0 == (_sizeInBytes & 7) + , "_sizeInBytes argument must be in multiples of 8-bytes (_sizeInBytes: %d)." + , _sizeInBytes + ); + + m_ptr = (uint64_t*)_ptr; + m_sizeInBytes = _sizeInBytes; + m_count = min(_count, getCapacity() ); + } + + inline bool BitArrayViewBase::get(uint32_t _idx) const + { + return 0 != (m_ptr[_idx/64] & (UINT64_C(1) << (_idx & 63) ) ); + } + + inline const void* BitArrayViewBase::getPtr() const + { + return m_ptr; + } + + inline const void* BitArrayViewBase::getTerm() const + { + return m_ptr + m_count / 64; + } + + inline uint32_t BitArrayViewBase::getCount() const + { + return m_count; + } + + inline uint32_t BitArrayViewBase::getCapacity() const + { + return m_sizeInBytes * 8; + } + + inline uint32_t BitArrayViewBase::find(uint32_t _numBits) const + { + return find(0, m_count, _numBits); + } + + inline uint32_t BitArrayViewBase::find(uint32_t _beginBit, uint32_t _endBit, uint32_t _numBits) const + { + BX_ASSERT(true + && _beginBit <= m_count + && _endBit <= m_count + && _beginBit <= _endBit + && _endBit-_beginBit >= _numBits + && _beginBit+_numBits <= m_count + && 0 < _numBits + , "Incorrect usage `%s`.\n" + "_beginBit %d, _endBit %d, _numBits %d, m_count %d [%d%d%d%d%d%d]" + , BX_FUNCTION + , _beginBit + , _endBit + , _numBits + , m_count + , _beginBit <= m_count + , _endBit <= m_count + , _beginBit <= _endBit + , _endBit-_beginBit >= _numBits + , _beginBit+_numBits <= m_count + , 0 < _numBits + ); + return findRange(_beginBit, _endBit, _numBits); + } + + inline uint32_t BitArrayViewBase::findSet(uint32_t _beginBit, uint32_t _endBit) const + { + BX_ASSERT(_beginBit <= _endBit && _endBit <= m_count + , "Invalid range [%d, %d), count %d." + , _beginBit + , _endBit + , m_count + ); + + uint32_t idx = _beginBit; + + while (idx < _endBit) + { + const uint32_t word = idx/64; + const uint64_t bits = m_ptr[word] & (~UINT64_C(0) << (idx & 63) ); + + if (0 != bits) + { + const uint32_t found = word*64 + findFirstSet(bits) - 1; + + return found < _endBit + ? found + : kInvalid + ; + } + + idx = (word + 1)*64; + } + + return kInvalid; + } + + inline uint32_t BitArrayViewBase::findClear(uint32_t _beginBit, uint32_t _endBit) const + { + BX_ASSERT(_beginBit <= _endBit && _endBit <= m_count + , "Invalid range [%d, %d), count %d." + , _beginBit + , _endBit + , m_count + ); + + uint32_t idx = _beginBit; + + while (idx < _endBit) + { + const uint32_t word = idx/64; + const uint64_t bits = ~m_ptr[word] & (~UINT64_C(0) << (idx & 63) ); + + if (0 != bits) + { + const uint32_t found = word*64 + findFirstSet(bits) - 1; + + return found < _endBit + ? found + : kInvalid + ; + } + + idx = (word + 1)*64; + } + + return kInvalid; + } + + inline uint32_t BitArrayViewBase::countBits() const + { + return countBits(0, m_count); + } + + inline bool BitArrayViewBase::testAny() const + { + return testAny(0, m_count); + } + + inline bool BitArrayViewBase::testAll() const + { + return testAll(0, m_count); + } + + inline bool BitArrayViewBase::testNone() const + { + return testNone(0, m_count); + } + + inline MutableBitArrayView::MutableBitArrayView() + { + set(nullptr, 0); + } + + inline MutableBitArrayView::MutableBitArrayView(void* _ptr, uint32_t _sizeInBytes, uint32_t _count) + : BitArrayViewBase(_ptr, _sizeInBytes, _count) + { + } + + inline void MutableBitArrayView::set(void* _src, uint32_t _count) + { + this->m_ptr = (uint64_t*)_src; + this->m_count = _count; + } + + inline void MutableBitArrayView::set(uint32_t _idx, bool _value) + { + BX_ASSERT(_idx <= m_count, "Out of bounds (idx %d, max %d)", _idx, m_count); + + const uint64_t mask = UINT64_C(1) << (_idx & 63); + + if (_value) + { + m_ptr[_idx/64] |= mask; + } + else + { + m_ptr[_idx/64] &= ~mask; + } + } + + inline void MutableBitArrayView::clear(bool _all) + { + const uint32_t sizeInBytes = _all + ? m_sizeInBytes + : alignUp(m_count, 8) / 8 + ; + memSet(m_ptr, 0, sizeInBytes); + } + + inline void* MutableBitArrayView::getPtr() + { + return this->m_ptr; + } + + inline const void* MutableBitArrayView::getPtr() const + { + return BitArrayViewBase::getPtr(); + } + + inline void* MutableBitArrayView::getTerm() + { + return this->m_ptr + m_count / 64; + } + + inline const void* MutableBitArrayView::getTerm() const + { + return BitArrayViewBase::getTerm(); + } + + template + inline BitArrayBaseT::BitArrayBaseT() + { + this->m_ptr = m_storage.getPtr(); + this->m_sizeInBytes = m_storage.getCapacity() * sizeof(uint64_t); + this->m_count = 0; + } + + template + BX_NO_INLINE inline BitArrayBaseT::BitArrayBaseT(const BitArrayViewBase& _src) + : MutableBitArrayView::MutableBitArrayView() + { + this->m_ptr = m_storage.getPtr(); + this->m_sizeInBytes = m_storage.getCapacity() * sizeof(uint64_t); + this->m_count = 0; + + set(_src); + } + + template + inline void BitArrayBaseT::set(const BitArrayViewBase& _src) + { + if (this != &_src) + { + this->setCount(_src.getCount() ); + + const uint32_t sizeInBytes = min(this->getCapacity(), _src.getCapacity() ) / 8; + bx::memCopy(this->m_ptr, _src.getPtr(), sizeInBytes); + } + } + + template + inline void BitArrayBaseT::set(uint32_t _idx, bool _value) + { + MutableBitArrayView::set(_idx, _value); + } + + template + inline void BitArrayBaseT::set(uint32_t _beginBit, uint32_t _endBit, bool _value) + { + MutableBitArrayView::set(_beginBit, _endBit, _value); + } + + template + inline void BitArrayBaseT::set(uint32_t _beginBit, uint32_t _endBit, uint64_t _value) + { + MutableBitArrayView::set(_beginBit, _endBit, _value); + } + + template + inline BitArrayBaseT::~BitArrayBaseT() + { + this->clear(); + } + + template + template + inline BitArrayBaseT& BitArrayBaseT::operator=(const BitArrayBaseT& _rhs) + { + if (this != &_rhs) + { + clear(); + } + + return *this; + } + + template + inline uint32_t BitArrayBaseT::getCapacity() const + { + return m_storage.getCapacity() * 64; + } + + template + inline void BitArrayBaseT::setCapacity(uint32_t _capacityInBits) + { + const uint32_t capacity = this->getCapacity(); + m_storage.setCapacity( + alignUp(max(capacity, _capacityInBits), 64)/64 + , capacity/64 + ); + this->m_ptr = m_storage.getPtr(); + this->m_sizeInBytes = m_storage.getCapacity() * sizeof(uint64_t); + } + + template + inline void BitArrayBaseT::setCount(uint32_t _countInBits) + { + if (_countInBits > getCapacity() ) + { + setCapacity(_countInBits); + } + + const uint32_t oldCountInBits = this->m_count; + this->m_count = _countInBits; + + if (oldCountInBits < _countInBits) + { + set(oldCountInBits, _countInBits, false); + } + } + + inline BitArray::BitArray(const BitArray& _other) + : BitArray(_other.m_storage.getAllocator(), _other) + { + } + + inline BitArray::BitArray(AllocatorI* _allocator) + : BitArrayBaseT>() + { + this->m_storage.setAllocator(_allocator); + } + + inline BitArray::BitArray(AllocatorI* _allocator, const BitArrayViewBase& _src) + : BitArray(_allocator) + { + set(_src); + } + + inline AllocatorI* BitArray::getAllocator() const + { + return this->m_storage.getAllocator(); + } + + inline BitArray& BitArray::operator=(const BitArray& _other) + { + if (this != &_other) + { + release(); + + this->m_storage.setAllocator(_other.m_storage.getAllocator() ); + this->set(_other); + } + + return *this; + } + + inline BitArray& BitArray::operator=(BitArray&& _other) + { + set(move(_other) ); + + return *this; + } + + inline void BitArray::set(const BitArrayViewBase& _src) + { + BitArrayBaseT>::set(_src); + } + + inline void BitArray::set(BitArray&& _other) + { + if (this != &_other) + { + release(); + + this->m_storage = move(_other.m_storage); + + this->m_ptr = _other.m_ptr; + this->m_sizeInBytes = _other.m_sizeInBytes; + this->m_count = _other.m_count; + + _other.m_ptr = NULL; + _other.m_sizeInBytes = 0; + _other.m_count = 0; + } + } + + inline void BitArray::release() + { + if (NULL != this->m_storage.getAllocator() ) + { + this->clear(); + this->m_storage.setCapacity(0, 0); + } + + this->m_ptr = this->m_storage.getPtr(); + this->m_sizeInBytes = 0; + this->m_count = 0; + } + + inline void BitArray::set(uint32_t _idx, bool _value) + { + BitArrayBaseT>::set(_idx, _value); + } + + inline void BitArray::set(uint32_t _beginBit, uint32_t _endBit, bool _value) + { + BitArrayBaseT>::set(_beginBit, _endBit, _value); + } + + inline void BitArray::set(uint32_t _beginBit, uint32_t _endBit, uint64_t _value) + { + BitArrayBaseT>::set(_beginBit, _endBit, _value); + } + + inline MippedBitArrayBase::MippedBitArrayBase() + : BitArrayViewBase() + , m_mip(NULL) + { + } + + inline void MippedBitArrayBase::set(uint32_t _idx, bool _value) + { + BX_ASSERT(_idx <= m_count, "Out of bounds (idx %d, max %d)", _idx, m_count); + + const uint32_t word = _idx/64; + const uint64_t mask = UINT64_C(1) << (_idx & 63); + + if (_value) + { + m_ptr[word] |= mask; + } + else + { + m_ptr[word] &= ~mask; + } + + const uint64_t mipMask = UINT64_C(1) << (word & 63); + + if (~UINT64_C(0) == m_ptr[word]) + { + m_mip[word/64] |= mipMask; + } + else + { + m_mip[word/64] &= ~mipMask; + } + } + + inline void MippedBitArrayBase::set(uint32_t _beginBit, uint32_t _endBit, bool _value) + { + if (_beginBit < _endBit) + { + MutableBitArrayView(m_ptr, m_sizeInBytes, m_count).set(_beginBit, _endBit, _value); + updateMip(_beginBit/64, alignUp(_endBit, 64)/64); + } + } + + inline void MippedBitArrayBase::set(uint32_t _beginBit, uint32_t _endBit, uint64_t _value) + { + if (_beginBit < _endBit) + { + MutableBitArrayView(m_ptr, m_sizeInBytes, m_count).set(_beginBit, _endBit, _value); + updateMip(_beginBit/64, alignUp(_endBit, 64)/64); + } + } + + inline void MippedBitArrayBase::blit(uint32_t _dstBit, uint32_t _srcBit, uint32_t _numBits) + { + if (0 < _numBits) + { + MutableBitArrayView(m_ptr, m_sizeInBytes, m_count).blit(_dstBit, _srcBit, _numBits); + updateMip(_dstBit/64, alignUp(_dstBit+_numBits, 64)/64); + } + } + + inline void MippedBitArrayBase::clear() + { + if (NULL != m_ptr) + { + MutableBitArrayView(m_ptr, m_sizeInBytes, m_count).clear(); + updateMip(0, m_sizeInBytes/8); + } + } + + inline uint32_t MippedBitArrayBase::findClear(uint32_t _beginBit, uint32_t _endBit) const + { + BX_ASSERT(_beginBit <= _endBit && _endBit <= m_count + , "Invalid range [%d, %d), count %d." + , _beginBit + , _endBit + , m_count + ); + + const uint32_t numWords = m_sizeInBytes/8; + const uint32_t numMipWords = getNumMipWords(numWords); + const uint32_t beginWord = _beginBit/64; + + for (uint32_t word = beginWord; word < numWords;) + { + uint32_t mipWord = word/64; + uint64_t avail = ~m_mip[mipWord] & (~UINT64_C(0) << (word & 63) ); + + while (0 == avail) + { + if (++mipWord >= numMipWords) + { + return kInvalid; + } + + avail = ~m_mip[mipWord]; + } + + word = mipWord*64 + findFirstSet(avail) - 1; + + if (word >= numWords) + { + return kInvalid; + } + + const uint64_t mask = word == beginWord + ? ~UINT64_C(0) << (_beginBit & 63) + : ~UINT64_C(0) + ; + const uint64_t bits = ~m_ptr[word] & mask; + + if (0 != bits) + { + const uint32_t found = word*64 + findFirstSet(bits) - 1; + + return found < _endBit + ? found + : kInvalid + ; + } + + ++word; + } + + return kInvalid; + } + + inline const uint64_t* MippedBitArrayBase::getMip() const + { + return m_mip; + } + + inline void MippedBitArrayBase::rebind(uint64_t* _ptr, uint32_t _numWords) + { + m_ptr = _ptr; + m_sizeInBytes = _numWords * sizeof(uint64_t); + m_mip = NULL; + + if (NULL != _ptr) + { + m_mip = _ptr + _numWords; + memSet(m_mip, 0, getNumMipWords(_numWords) * sizeof(uint64_t) ); + setMipTail(); + } + } + + inline void MippedBitArrayBase::moveMip(uint64_t* _ptr, uint32_t _oldNumWords, uint32_t _numWords) + { + if (NULL == _ptr + || 0 == _oldNumWords) + { + rebind(_ptr, _numWords); + return; + } + + const uint32_t oldNumMipWords = getNumMipWords(_oldNumWords); + const uint32_t numMipWords = getNumMipWords(_numWords); + uint64_t* mip = _ptr + _numWords; + + memMove(mip, _ptr + _oldNumWords, min(oldNumMipWords, numMipWords) * sizeof(uint64_t) ); + + if (numMipWords > oldNumMipWords) + { + memSet(mip + oldNumMipWords, 0, (numMipWords - oldNumMipWords) * sizeof(uint64_t) ); + } + + if (0 != (_oldNumWords & 63) ) + { + mip[_oldNumWords/64] &= (UINT64_C(1) << (_oldNumWords & 63) ) - 1; + } + + m_ptr = _ptr; + m_sizeInBytes = _numWords * sizeof(uint64_t); + m_mip = mip; + setMipTail(); + } + + inline void MippedBitArrayBase::updateMip(uint32_t _beginWord, uint32_t _endWord) + { + for (uint32_t ii = _beginWord; ii < _endWord; ++ii) + { + const uint64_t mipMask = UINT64_C(1) << (ii & 63); + + if (~UINT64_C(0) == m_ptr[ii]) + { + m_mip[ii/64] |= mipMask; + } + else + { + m_mip[ii/64] &= ~mipMask; + } + } + } + + inline void MippedBitArrayBase::setMipTail() + { + const uint32_t numWords = m_sizeInBytes/8; + const uint32_t tail = numWords & 63; + + if (0 != tail) + { + m_mip[numWords/64] |= ~UINT64_C(0) << tail; + } + } + + template + inline MippedBitArrayT::MippedBitArrayT() + { + const uint32_t total = m_storage.getCapacity(); + + m_numWords = total - (total + 64)/65; + this->m_count = 0; + rebind(m_storage.getPtr(), m_numWords); + } + + template + inline uint32_t MippedBitArrayT::getCapacity() const + { + return m_numWords * 64; + } + + template + inline void MippedBitArrayT::setCapacity(uint32_t _capacityInBits) + { + const uint32_t numWords = alignUp(_capacityInBits, 64)/64; + + if (numWords == m_numWords) + { + return; + } + + const uint32_t oldNumWords = m_numWords; + const uint32_t total = numWords + getNumMipWords(numWords); + + if (numWords > oldNumWords) + { + m_storage.setCapacity(total, total); + m_numWords = numWords; + moveMip(m_storage.getPtr(), oldNumWords, numWords); + } + else + { + moveMip(m_storage.getPtr(), oldNumWords, numWords); + m_storage.setCapacity(total, total); + m_numWords = numWords; + this->m_ptr = m_storage.getPtr(); + this->m_mip = this->m_ptr + numWords; + } + } + + template + inline void MippedBitArrayT::setCount(uint32_t _countInBits) + { + if (_countInBits > getCapacity() ) + { + setCapacity(_countInBits); + } + + const uint32_t oldCountInBits = this->m_count; + this->m_count = _countInBits; + + if (oldCountInBits < _countInBits) + { + set(oldCountInBits, _countInBits, false); + } + } + + inline MippedBitArray::MippedBitArray(AllocatorI* _allocator) + : MippedBitArrayT>() + { + this->m_storage.setAllocator(_allocator); + } + + inline AllocatorI* MippedBitArray::getAllocator() const + { + return this->m_storage.getAllocator(); + } + + +} // namespace bx diff --git a/include/bx/inline/bufferstorage.inl b/include/bx/inline/bufferstorage.inl new file mode 100644 index 0000000..e3ae602 --- /dev/null +++ b/include/bx/inline/bufferstorage.inl @@ -0,0 +1,590 @@ +/* + * Copyright 2011-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bx#license-bsd-2-clause + */ + +#ifndef BX_BUFFERSTORAGE_H_HEADER_GUARD +# error "Must be included from bx/bufferstorage.h!" +#endif // BX_BUFFERSTORAGE_H_HEADER_GUARD + +namespace bx +{ + inline void dbgGraph(uint32_t _total, uint32_t _begin, uint32_t _count, char _ch) + { + const char* empty = "................................"; + char rep[32]; + for (uint32_t ii = 0; ii < BX_COUNTOF(rep); ++ii) { rep[ii] = _ch; } + + BX_UNUSED("\t|%.*s%.*s%.*s|\n" + , _begin, empty + , _count, rep + , _total-_begin-_count, empty + ); + } + + inline void sanitize(uint32_t _total, uint32_t& _idx, uint32_t& _count) + { + _idx = min(_total, _idx); + _count = min(_total - _idx, _count); + } + + inline bool overlap(const void* _a, size_t _sizeInBytesA, const void* _b, size_t _sizeInBytesB) + { + const uintptr_t aa = uintptr_t(_a); + const uintptr_t bb = uintptr_t(_b); + return aa + _sizeInBytesA > bb + && bb + _sizeInBytesB > aa + ; + } + + template + inline constexpr bool overlap(const Ty* _a, const Ty* _b, uint32_t _count) + { + const size_t sizeInBytes = sizeof(Ty)*_count; + return overlap(_a, sizeInBytes, _b, sizeInBytes); + } + + template + inline uint32_t StorageTraits::copy(Ty* _dst, const Ty* _src, uint32_t _count) + { + if (BX_ENABLED(isTriviallyCopyAssignable() ) ) + { + memMove(_dst, _src, _count * sizeof(Ty) ); + } + else + { + uint32_t from = 0; + uint32_t mask = 0; + + if (_src + _count > _dst + && _src < _dst) + { + from = _count; + mask = UINT32_MAX; + } + + for (uint32_t ii = 0; ii < _count; ++ii) + { + const uint32_t idx = ( (~from + 1) + ii) ^ mask; + _dst[idx] = _src[idx]; + } + } + + return _count; + } + + template + inline void StorageTraits::move(Ty* _dst, Ty* _src, uint32_t _count) + { + if (BX_ENABLED(isTriviallyMoveAssignable() ) ) + { + memMove(_dst, _src, _count * sizeof(Ty) ); + } + else + { + uint32_t from = 0; + uint32_t mask = 0; + + if (_src + _count > _dst + && _src < _dst) + { + from = _count; + mask = UINT32_MAX; + } + + for (uint32_t ii = 0; ii < _count; ++ii) + { + const uint32_t idx = ( (~from + 1) + ii) ^ mask; + _dst[idx] = bx::move(_src[idx]); + } + } + } + + template + inline EnableIfType::value + && !IsConstructibleT::value + , void> + StorageTraits::constructDefault(Ty* _ptr, uint32_t _count) + { + for (Ty *ptr = _ptr, *term = _ptr + _count; ptr < term; ++ptr) + { + BX_PLACEMENT_NEW(ptr, Ty); + } + } + + template + inline EnableIfType::value + && IsConstructibleT::value + , void> + StorageTraits::constructDefault(Ty* _ptr, uint32_t _count) + { + for (Ty *ptr = _ptr, *term = _ptr + _count; ptr < term; ++ptr) + { + BX_PLACEMENT_NEW(ptr, Ty)(InitNone); + } + } + + template + inline EnableIfType::value + , void> + StorageTraits::constructDefault(Ty* _ptr, uint32_t _count) + { + BX_UNUSED(_ptr, _count); + } + + template + inline uint32_t StorageTraits::constructEmplace(Ty* _ptr, uint32_t _count, ArgsT&&... _args) + { + for (Ty *ptr = _ptr, *term = _ptr + _count; ptr < term; ++ptr) + { + BX_PLACEMENT_NEW(ptr, Ty)(forward(_args)...); + } + + return _count; + } + + template + inline void StorageTraits::constructMove(Ty* _dst, Ty* _src, uint32_t _count) + { + if (BX_ENABLED(isTriviallyMoveConstructible() ) ) + { + memMove(_dst, _src, _count * sizeof(Ty) ); + } + else if (BX_ENABLED(isMoveConstructible() ) ) + { + for (uint32_t ii = 0; ii < _count; ++ii) + { + BX_PLACEMENT_NEW(&_dst[ii], Ty)(bx::move(_src[ii]) ); + } + } + else + { + constructDefault(_dst, _count); + move(_dst, _src, _count); + } + } + + template + inline void StorageTraits::destruct(Ty* _ptr, uint32_t _count) + { + if (!BX_ENABLED(isTriviallyDestructible() ) ) + { + for (Ty *ptr = _ptr, *term = _ptr + _count; ptr < term; ++ptr) + { + ptr->~Ty(); + } + } + } + + //--- + + template + inline StorageViewT::StorageViewT(const Ty* _ptr, uint32_t _count) + : m_ptr(_ptr) + , m_count(_count) + { + } + + template + inline StorageViewT::StorageViewT(const StorageViewT& _other) + : m_ptr(_other.m_ptr) + , m_count(_other.m_count) + { + } + + template + inline StorageViewT::~StorageViewT() + { + } + + template + inline StorageViewT& StorageViewT::operator=(const StorageViewT& _other) + { + m_ptr = _other.m_ptr; + m_count = _other.m_count; + + return *this; + } + + template + inline StorageViewT& StorageViewT::operator=(StorageViewT&& _other) + { + m_ptr = _other.m_ptr; + m_count = _other.m_count; + + _other.m_ptr = NULL; + _other.m_count = 0; + + return *this; + } + + template + template + inline void StorageViewT::setCapacity(StorageT& _storage, uint32_t _capacity) + { + const uint32_t count = m_count; + _storage.setCapacity(max(count, _capacity), count); + m_ptr = _storage.getPtr(); + } + + template + inline uint32_t StorageViewT::getCount() const + { + return m_count; + } + + template + template + inline void StorageViewT::setCount(StorageT& _storage, uint32_t _count) + { + if (_count > _storage.getCapacity() ) + { + setCapacity(_storage, _count); + } + + { + uint32_t idx = 0; + sanitize(_storage.getCapacity(), idx, _count); + } + + const uint32_t oldCount = m_count; + + if (oldCount > _count) + { + return remove(_storage, _count, kUnordered, UINT32_MAX); + } + + if (oldCount < _count) + { + const uint32_t diff = _count - oldCount; + + dbgGraph(_count, oldCount, diff, 'C'); + StorageTraits::constructDefault(_storage.getPtr(oldCount), diff); + } + + m_count = _count; + } + + template + template + inline void StorageViewT::add(StorageT& _storage, uint32_t _pos, const Ty* _ptr, uint32_t _count) + { + if (0 == _count) + { + return; + } + + const uint32_t dstPos = carve(_storage, _pos, _count, true); + + BX_ASSERT(!overlap(_storage.getPtr(dstPos), _ptr, _count), ""); + + dbgGraph(m_count+_count, dstPos, _count, 'c'); + + m_count += StorageTraits::copy(_storage.getPtr(dstPos), _ptr, _count); + } + + template + template + inline void StorageViewT::add(StorageT& _storage, uint32_t _pos, EmplaceTag, ArgsT&&... _args) + { + const uint32_t oldCount = m_count; + const uint32_t dstPos = carve(_storage, _pos, 1, false); + Ty* dst = _storage.getPtr(dstPos); + + if (dstPos < oldCount) + { + dbgGraph(oldCount+1, dstPos, 1, 'D'); + + StorageTraits::destruct(dst, 1); + } + + dbgGraph(oldCount+1, dstPos, 1, 'E'); + + m_count += StorageTraits::constructEmplace(dst, 1, forward(_args)...); + } + + template + template + inline void StorageViewT::remove(StorageT& _storage, uint32_t _pos, Ordering _ordering, uint32_t _count) + { + if (0 == _count) + { + return; + } + + const uint32_t oldCount = m_count; + sanitize(oldCount, _pos, _count); + + const uint32_t end = _pos + _count; + const uint32_t newCount = oldCount - _count; + + uint32_t move, from; + + if (_ordering == kOrdered) + { + move = oldCount - end; + from = end; + } + else + { + move = min(oldCount - end, _count); + from = oldCount - move; + } + + if (0 == _count) + { + return; + } + + if (0 != move) + { + dbgGraph(oldCount, from, move, 'F'); + dbgGraph(oldCount, _pos, move, 'M'); + + StorageTraits::move(_storage.getPtr(_pos), _storage.getPtr(from), move); + } + + dbgGraph(oldCount, newCount, _count, 'D'); + StorageTraits::destruct(_storage.getPtr(newCount), _count); + + m_count = newCount; + } + + template + template + inline uint32_t StorageViewT::carve(StorageT& _storage, uint32_t _pos, uint32_t _count, bool _init) + { + const uint32_t dstCount = m_count; + const uint32_t newCount = _count + dstCount; + + if (newCount > _storage.getCapacity() ) + { + _storage.setCapacity(max(dstCount, newCount), dstCount); + m_ptr = _storage.getPtr(); + } + + BX_ASSERT(newCount <= _storage.getCapacity() + , "Out-of-bounds (count: %d, capacity: %d)!" + , newCount, _storage.getCapacity() + ); + + const uint32_t dstPos = min(_pos, dstCount); + const uint32_t moveCount = dstCount - dstPos; + const uint32_t newPos = dstPos + _count; + + const bool needCtor = _init || dstPos != dstCount; + + dbgGraph(newCount, dstCount, _count, needCtor ? 'C' : '?'); + + if (needCtor) + { + StorageTraits::constructDefault(_storage.getPtr(dstCount), _count); + } + + if (0 != moveCount) + { + dbgGraph(newCount, dstPos, moveCount, 'F'); + dbgGraph(newCount, newPos, moveCount, 'M'); + + StorageTraits::move(_storage.getPtr(newPos), _storage.getPtr(dstPos), moveCount); + } + + return dstPos; + } + + //--- + + template + inline FixedStorageT::FixedStorageT() + { + } + + template + inline FixedStorageT::~FixedStorageT() + { + } + + template + inline constexpr uint32_t FixedStorageT::getCapacity() const + { + return CapacityT; + } + + template + inline void FixedStorageT::setCapacity(uint32_t /*_capacity*/, uint32_t /*_count*/) + { + } + + template + inline Ty* FixedStorageT::getPtr(uint32_t _idx) + { + BX_ASSERT(_idx <= CapacityT, "Out-of-bounds (_idx: %d, max %d)!", _idx, CapacityT); + return addressOf(m_data) + _idx; + } + + template + inline const Ty* FixedStorageT::getPtr(uint32_t _idx) const + { + BX_ASSERT(_idx <= CapacityT, "Out-of-bounds (_idx: %d, max %d)!", _idx, CapacityT); + return addressOf(m_data) + _idx; + } + + template + inline Ty* FixedStorageT::getTerm() + { + return getPtr(CapacityT); + } + + template + inline const Ty* FixedStorageT::getTerm() const + { + return getPtr(CapacityT); + } + + template + inline FixedStorageT& FixedStorageT::operator=(const FixedStorageT& _other) + { + memCopy(m_data, _other.m_data, sizeof(m_data) ); + return *this; + } + + template + inline FixedStorageT& FixedStorageT::operator=(FixedStorageT&& _other) + { + memCopy(m_data, _other.m_data, sizeof(m_data) ); + return *this; + } + + template + inline DynamicStorageT::DynamicStorageT() + : m_ptr(NULL) + , m_capacity(0) + , m_allocator(NULL) + { + } + + template + inline void DynamicStorageT::setAllocator(AllocatorI* _allocator) + { + BX_ASSERT(false + || NULL == m_ptr + || NULL == m_allocator + , "Allocator is already set, or storage is not empty!" + ); + + m_allocator = _allocator; + } + + template + inline AllocatorI* DynamicStorageT::getAllocator() const + { + return m_allocator; + } + + template + inline DynamicStorageT::~DynamicStorageT() + { + if (NULL != m_ptr) + { + bx::free(m_allocator, m_ptr, BX_ALIGNOF(Ty) ); + } + } + + template + inline uint32_t DynamicStorageT::getCapacity() const + { + return m_capacity; + } + + template + inline void DynamicStorageT::setCapacity(uint32_t _capacity, uint32_t _count) + { + const uint32_t minCapacity = max(_capacity, _count); + const uint32_t capacity = minCapacity == 0 ? 0 : alignUp(minCapacity, 16); + + if (capacity == m_capacity) + { + return; + } + + if (0 == capacity) + { + bx::free(m_allocator, m_ptr, BX_ALIGNOF(Ty) ); + m_ptr = NULL; + } + else if (BX_ENABLED(isTriviallyMoveConstructible() ) ) + { + m_ptr = (Ty*)bx::realloc(m_allocator, m_ptr, sizeof(Ty)*capacity, BX_ALIGNOF(Ty) ); + } + else + { + Ty* ptr = (Ty*)bx::alloc(m_allocator, sizeof(Ty)*capacity, BX_ALIGNOF(Ty) ); + + if (0 < _count) + { + StorageTraits::constructMove(ptr, m_ptr, _count); + StorageTraits::destruct(m_ptr, _count); + } + + if (NULL != m_ptr) + { + bx::free(m_allocator, m_ptr, BX_ALIGNOF(Ty) ); + } + + m_ptr = ptr; + } + + m_capacity = capacity; + } + + template + inline Ty* DynamicStorageT::getPtr(uint32_t _idx) + { + if (NULL == m_ptr) + { + return NULL; + } + + return m_ptr + _idx; + } + + template + inline const Ty* DynamicStorageT::getPtr(uint32_t _idx) const + { + return const_cast*>(this)->getPtr(_idx); + } + + template + inline Ty* DynamicStorageT::getTerm() + { + return getPtr(getCapacity() ); + } + + template + inline const Ty* DynamicStorageT::getTerm() const + { + return getPtr(getCapacity() ); + } + + template + inline DynamicStorageT& DynamicStorageT::operator=(DynamicStorageT&& _other) + { + if (NULL != m_ptr) + { + bx::free(m_allocator, m_ptr, BX_ALIGNOF(Ty) ); + } + + m_ptr = _other.m_ptr; + m_capacity = _other.m_capacity; + m_allocator = _other.m_allocator; + + _other.m_ptr = NULL; + _other.m_capacity = 0; + + return *this; + } + +} // namespace bx diff --git a/include/bx/inline/handlealloc.inl b/include/bx/inline/handlealloc.inl index fa5cd54..f18b880 100644 --- a/include/bx/inline/handlealloc.inl +++ b/include/bx/inline/handlealloc.inl @@ -713,4 +713,104 @@ namespace bx m_alloc.reset(); } + inline HandleAlloc2::HandleAlloc2(MippedBitArrayBase& _bits, uint16_t _maxHandles) + : m_bits(_bits) + , m_maxHandles(_maxHandles) + , m_numHandles(0) + { + BX_ASSERT(_bits.getCount() >= _maxHandles, "Bit array holds %d bits, %d handles requested.", _bits.getCount(), _maxHandles); + reset(); + } + + inline HandleAlloc2::HandleAlloc2(InitNoneTag, MippedBitArrayBase& _bits, uint16_t _maxHandles) + : m_bits(_bits) + , m_maxHandles(_maxHandles) + , m_numHandles(0) + { + } + + inline uint16_t HandleAlloc2::getNumHandles() const + { + return m_numHandles; + } + + inline uint16_t HandleAlloc2::getMaxHandles() const + { + return m_maxHandles; + } + + inline uint16_t HandleAlloc2::alloc() + { + const uint32_t idx = m_bits.findClear(0, m_maxHandles); + + if (kInvalid == idx) + { + return kInvalidHandle; + } + + m_bits.set(idx, true); + ++m_numHandles; + + return uint16_t(idx); + } + + inline bool HandleAlloc2::isValid(uint16_t _handle) const + { + return true + && _handle < m_maxHandles + && m_bits.get(_handle) + ; + } + + inline void HandleAlloc2::free(uint16_t _handle) + { + if (isValid(_handle) ) + { + m_bits.set(_handle, false); + --m_numHandles; + } + } + + inline void HandleAlloc2::reset() + { + m_bits.set(0, m_maxHandles, false); + m_numHandles = 0; + } + + inline uint16_t HandleAlloc2::findFirst() const + { + return findFrom(0); + } + + inline uint16_t HandleAlloc2::findNext(uint16_t _handle) const + { + return findFrom(uint32_t(_handle) + 1); + } + + inline const BitArrayViewBase& HandleAlloc2::getBits() const + { + return m_bits; + } + + inline uint16_t HandleAlloc2::findFrom(uint32_t _start) const + { + const uint32_t idx = _start < m_maxHandles + ? m_bits.findSet(_start, m_maxHandles) + : kInvalid + ; + + return kInvalid == idx + ? kInvalidHandle + : uint16_t(idx) + ; + } + + template + inline HandleAlloc2T::HandleAlloc2T() + : HandleAlloc2(InitNone, m_storage, MaxHandlesT) + { + m_storage.setCount(MaxHandlesT); + } + + } // namespace bx diff --git a/scripts/bx.natvis b/scripts/bx.natvis index e1394d2..9e0bc44 100644 --- a/scripts/bx.natvis +++ b/scripts/bx.natvis @@ -7,4 +7,129 @@ m_ptr,[m_len] + + {{ handles={m_numHandles}/{m_maxHandles} }} + + m_numHandles + m_maxHandles + + m_numHandles + (unsigned short*)(this+1) + + + + + + {{ handles={m_numHandles}/{m_maxHandles} }} + + m_numHandles + m_maxHandles + m_bits + + + + + idx + + idx++ + + + + + + + {{ handles={m_numHandles}/{m_maxHandles} }} + + *(bx::HandleAlloc2*)this + + + + + {{ Capacity={$T2} }} + + + $T2 + ($T1*)m_data + + + + + + {{ empty }} + {{ Capacity={m_capacity} ptr={(void*)m_ptr} }} + + m_capacity + m_allocator + + m_capacity + m_ptr + + + + + + {{ count={m_count} }} + + m_count + m_ptr + + m_count + m_ptr + + + + + + {{ empty }} + {{ count={m_count} bytes={m_sizeInBytes} }} + + m_count + m_sizeInBytes + m_ptr + + m_sizeInBytes / 8 + m_ptr + + + + + + {{ empty }} + {{ count={m_count} bytes={m_sizeInBytes} }} + + m_count + m_sizeInBytes + m_storage.m_allocator + m_storage.m_capacity + + m_sizeInBytes / 8 + m_ptr + + + + + + + {{ empty }} + {{ count={m_count} bytes={m_sizeInBytes} }} + + m_count + m_sizeInBytes + m_ptr + m_mip,[(m_sizeInBytes/8+63)/64] + + m_sizeInBytes / 8 + m_ptr + + + + + + {{ empty }} + {{ count={m_count} bytes={m_sizeInBytes} }} + + *(bx::MippedBitArrayBase*)this + + + diff --git a/src/bitarray.cpp b/src/bitarray.cpp new file mode 100644 index 0000000..3647f3e --- /dev/null +++ b/src/bitarray.cpp @@ -0,0 +1,846 @@ +/* + * Copyright 2010-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bx#license-bsd-2-clause + */ + +#include +#include +#include + +namespace bx +{ + inline void bitSetSet(uint64_t* _ptr, uint32_t _idx, bool _value) + { + const simd32_t mask63 = simd32_splat(uint32_t(63) ); + const simd32_t bit = simd32_splat(_idx); + const simd32_t idxVec = simd32_x32_srl(bit, 6); + const simd32_t shiftVec = simd32_and(bit, mask63); + const uint32_t idx = idxVec.u32; + const uint32_t shift = shiftVec.u32; + const simd64_t zero = simd64_zero(); + const simd64_t one = simd64_splat(uint64_t(1) ); + const simd64_t mask = simd64_x64_sll(one, int(shift) ); + const simd64_t cmask = simd64_not(mask); + const simd64_t valVec = simd64_splat(uint64_t(_value) ); + const simd64_t neg = simd64_u64_sub(zero, valVec); + const simd64_t orbit = simd64_and(mask, neg); + const simd64_t prev = simd64_splat(_ptr[idx]); + const simd64_t cleared = simd64_and(prev, cmask); + const simd64_t result = simd64_or(cleared, orbit); + + _ptr[idx] = result.u64; + } + + inline void bitSetSet(uint64_t* _ptr, uint32_t _beginBit, uint32_t _endBit, bool _value) + { + if (_beginBit >= _endBit) + { + return; + } + + const simd32_t mask63 = simd32_splat(uint32_t(63) ); + const simd32_t one = simd32_splat(1u); + const simd32_t bitB = simd32_splat(_beginBit); + const simd32_t bitEnd = simd32_splat(_endBit); + const simd32_t bitE = simd32_u32_satsub(bitEnd, one); + const simd32_t idxBVec = simd32_x32_srl(bitB, 6); + const simd32_t shiftBVec = simd32_and(bitB, mask63); + const simd32_t idxEVec = simd32_x32_srl(bitE, 6); + const simd32_t shiftEVec = simd32_and(bitEnd, mask63); + const uint32_t idxB = idxBVec.u32; + const uint32_t shiftB = shiftBVec.u32; + const uint32_t idxE = idxEVec.u32; + const uint32_t shiftE = shiftEVec.u32; + const simd64_t zero = simd64_zero(); + const simd64_t allOnes = simd64_splat(UINT64_MAX); + const simd64_t maskB = simd64_x64_sll(allOnes, int(shiftB) ); + const simd64_t maskE = simd64_x64_srl(allOnes, int(64-shiftE) ); + const simd64_t valVec = simd64_splat(uint64_t(_value) ); + const simd64_t orbits = simd64_u64_sub(zero, valVec); + const simd64_t orbitsB = simd64_and(maskB, orbits); + const simd64_t orbitsE = simd64_and(maskE, orbits); + const simd64_t cmaskB = simd64_not(maskB); + const simd64_t cmaskE = simd64_not(maskE); + + if (idxB == idxE) + { + const simd64_t maskBE = simd64_and(maskB, maskE); + const simd64_t cmaskBE = simd64_not(maskBE); + const simd64_t orbitsBE = simd64_and(orbitsB, orbitsE); + const simd64_t prev = simd64_splat(_ptr[idxB]); + const simd64_t cleared = simd64_and(prev, cmaskBE); + const simd64_t result = simd64_or(cleared, orbitsBE); + _ptr[idxB] = result.u64; + return; + } + + { + const simd64_t prev = simd64_splat(_ptr[idxB]); + const simd64_t cleared = simd64_and(prev, cmaskB); + const simd64_t result = simd64_or(cleared, orbitsB); + _ptr[idxB] = result.u64; + } + + for (uint32_t idx = idxB+1; idx < idxE; ++idx) + { + _ptr[idx] = orbits.u64; + } + + { + const simd64_t prev = simd64_splat(_ptr[idxE]); + const simd64_t cleared = simd64_and(prev, cmaskE); + const simd64_t result = simd64_or(cleared, orbitsE); + _ptr[idxE] = result.u64; + } + } + + inline bool bitSetGet(const uint64_t* _ptr, uint32_t _idx) + { + const simd32_t mask63 = simd32_splat(uint32_t(63) ); + const simd32_t bitVec = simd32_splat(_idx); + const simd32_t idxVec = simd32_x32_srl(bitVec, 6); + const simd32_t shiftVec = simd32_and(bitVec, mask63); + const uint32_t idx = idxVec.u32; + const uint32_t shift = shiftVec.u32; + const simd64_t one = simd64_splat(uint64_t(1) ); + const simd64_t bit = simd64_x64_sll(one, int(shift) ); + const simd64_t prev = simd64_splat(_ptr[idx]); + const simd64_t masked = simd64_and(prev, bit); + + return 0 != masked.u64; + } + + inline uint32_t bitSetCountBits(const uint64_t* _ptr, uint32_t _beginBit, uint32_t _endBit) + { + if (_beginBit >= _endBit) + { + return 0; + } + + const simd32_t mask63 = simd32_splat(uint32_t(63) ); + const simd32_t one = simd32_splat(1u); + const simd32_t bitB = simd32_splat(_beginBit); + const simd32_t bitEnd = simd32_splat(_endBit); + const simd32_t bitE = simd32_u32_satsub(bitEnd, one); + const simd32_t idxBVec = simd32_x32_srl(bitB, 6); + const simd32_t shiftBVec = simd32_and(bitB, mask63); + const simd32_t idxEVec = simd32_x32_srl(bitE, 6); + const simd32_t shiftEVec = simd32_and(bitEnd, mask63); + const uint32_t idxB = idxBVec.u32; + const uint32_t shiftB = shiftBVec.u32; + const uint32_t idxE = idxEVec.u32; + const uint32_t shiftE = shiftEVec.u32; + const simd64_t allOnes = simd64_splat(UINT64_MAX); + const simd64_t maskB = simd64_x64_sll(allOnes, int(shiftB) ); + const simd64_t maskE = simd64_x64_srl(allOnes, int(64-shiftE) ); + + if (idxB == idxE) + { + const simd64_t mask = simd64_and(maskB, maskE); + const simd64_t prev = simd64_splat(_ptr[idxB]); + const simd64_t masked = simd64_and(prev, mask); + return countBits(masked.u64); + } + + const simd64_t prevB = simd64_splat(_ptr[idxB]); + const simd64_t maskedB = simd64_and(prevB, maskB); + uint32_t count = countBits(maskedB.u64); + + for (uint32_t idx = idxB+1; idx < idxE; ++idx) + { + count += countBits(_ptr[idx]); + } + + const simd64_t prevE = simd64_splat(_ptr[idxE]); + const simd64_t maskedE = simd64_and(prevE, maskE); + count += countBits(maskedE.u64); + + return count; + } + + inline bool bitSetTestAny(const uint64_t* _ptr, uint32_t _beginBit, uint32_t _endBit) + { + if (_beginBit >= _endBit) + { + return false; + } + + const simd32_t mask63 = simd32_splat(uint32_t(63) ); + const simd32_t one = simd32_splat(1u); + const simd32_t bitB = simd32_splat(_beginBit); + const simd32_t bitEnd = simd32_splat(_endBit); + const simd32_t bitE = simd32_u32_satsub(bitEnd, one); + const simd32_t idxBVec = simd32_x32_srl(bitB, 6); + const simd32_t shiftBVec = simd32_and(bitB, mask63); + const simd32_t idxEVec = simd32_x32_srl(bitE, 6); + const simd32_t shiftEVec = simd32_and(bitEnd, mask63); + const uint32_t idxB = idxBVec.u32; + const uint32_t shiftB = shiftBVec.u32; + const uint32_t idxE = idxEVec.u32; + const uint32_t shiftE = shiftEVec.u32; + const simd64_t allOnes = simd64_splat(UINT64_MAX); + const simd64_t maskB = simd64_x64_sll(allOnes, int(shiftB) ); + const simd64_t tmp0 = simd64_x64_srl(allOnes, int(63-shiftE) ); + const simd64_t maskE = simd64_x64_srl(tmp0, 1); + + if (idxB == idxE) + { + const simd64_t mask = simd64_and(maskB, maskE); + const simd64_t prev = simd64_splat(_ptr[idxB]); + const simd64_t masked = simd64_and(prev, mask); + return 0 != masked.u64; + } + + { + const simd64_t prev = simd64_splat(_ptr[idxB]); + const simd64_t masked = simd64_and(prev, maskB); + if (0 != masked.u64) + { + return true; + } + } + + for (uint32_t idx = idxB+1; idx < idxE; ++idx) + { + if (0 != _ptr[idx]) + { + return true; + } + } + + const simd64_t prev = simd64_splat(_ptr[idxE]); + const simd64_t masked = simd64_and(prev, maskE); + return 0 != masked.u64; + } + + inline bool bitSetTestAll(const uint64_t* _ptr, uint32_t _beginBit, uint32_t _endBit) + { + if (_beginBit >= _endBit) + { + return true; + } + + const simd32_t mask63 = simd32_splat(uint32_t(63) ); + const simd32_t one = simd32_splat(1u); + const simd32_t bitB = simd32_splat(_beginBit); + const simd32_t bitEnd = simd32_splat(_endBit); + const simd32_t bitE = simd32_u32_satsub(bitEnd, one); + const simd32_t idxBVec = simd32_x32_srl(bitB, 6); + const simd32_t shiftBVec = simd32_and(bitB, mask63); + const simd32_t idxEVec = simd32_x32_srl(bitE, 6); + const simd32_t shiftEVec = simd32_and(bitEnd, mask63); + const uint32_t idxB = idxBVec.u32; + const uint32_t shiftB = shiftBVec.u32; + const uint32_t idxE = idxEVec.u32; + const uint32_t shiftE = shiftEVec.u32; + const simd64_t allOnes = simd64_splat(UINT64_MAX); + const simd64_t maskB = simd64_x64_sll(allOnes, int(shiftB) ); + const simd64_t tmp0 = simd64_x64_srl(allOnes, int(63-shiftE) ); + const simd64_t maskE = simd64_x64_srl(tmp0, 1); + + if (idxB == idxE) + { + const simd64_t mask = simd64_and(maskB, maskE); + const simd64_t prev = simd64_splat(_ptr[idxB]); + const simd64_t masked = simd64_and(prev, mask); + return mask.u64 == masked.u64; + } + + { + const simd64_t prev = simd64_splat(_ptr[idxB]); + const simd64_t masked = simd64_and(prev, maskB); + if (maskB.u64 != masked.u64) + { + return false; + } + } + + for (uint32_t idx = idxB+1; idx < idxE; ++idx) + { + if (UINT64_MAX != _ptr[idx]) + { + return false; + } + } + + const simd64_t prev = simd64_splat(_ptr[idxE]); + const simd64_t masked = simd64_and(prev, maskE); + return maskE.u64 == masked.u64; + } + + inline bool bitSetTestNone(const uint64_t* _ptr, uint32_t _beginBit, uint32_t _endBit) + { + if (_beginBit >= _endBit) + { + return true; + } + + const simd32_t mask63 = simd32_splat(uint32_t(63) ); + const simd32_t one = simd32_splat(1u); + const simd32_t bitB = simd32_splat(_beginBit); + const simd32_t bitEnd = simd32_splat(_endBit); + const simd32_t bitE = simd32_u32_satsub(bitEnd, one); + const simd32_t idxBVec = simd32_x32_srl(bitB, 6); + const simd32_t shiftBVec = simd32_and(bitB, mask63); + const simd32_t idxEVec = simd32_x32_srl(bitE, 6); + const simd32_t shiftEVec = simd32_and(bitEnd, mask63); + const uint32_t idxB = idxBVec.u32; + const uint32_t shiftB = shiftBVec.u32; + const uint32_t idxE = idxEVec.u32; + const uint32_t shiftE = shiftEVec.u32; + const simd64_t allOnes = simd64_splat(UINT64_MAX); + const simd64_t maskB = simd64_x64_sll(allOnes, int(shiftB) ); + const simd64_t tmp0 = simd64_x64_srl(allOnes, int(63-shiftE) ); + const simd64_t maskE = simd64_x64_srl(tmp0, 1); + + if (idxB == idxE) + { + const simd64_t mask = simd64_and(maskB, maskE); + const simd64_t prev = simd64_splat(_ptr[idxB]); + const simd64_t masked = simd64_and(prev, mask); + return 0 == masked.u64; + } + + { + const simd64_t prev = simd64_splat(_ptr[idxB]); + const simd64_t masked = simd64_and(prev, maskB); + if (0 != masked.u64) + { + return false; + } + } + + for (uint32_t idx = idxB+1; idx < idxE; ++idx) + { + if (0 != _ptr[idx]) + { + return false; + } + } + + const simd64_t prev = simd64_splat(_ptr[idxE]); + const simd64_t masked = simd64_and(prev, maskE); + return 0 == masked.u64; + } + + inline uint64_t extractBits(uint64_t _input, uint32_t _pos, uint64_t _mask) + { + const uint64_t bits = _input >> _pos; + return bits & _mask; + } + + inline uint64_t replaceBits(uint64_t _input, uint32_t _pos, uint64_t _mask, uint64_t _bits) + { + const uint64_t bits = (_bits & _mask) << _pos; + const uint64_t old = _input & ~(_mask << _pos); + return bits | old; + } + + inline void bitSet_setBits(uint64_t* _ptr, uint32_t _pos, uint64_t _bits, uint32_t _numBits) + { + BX_ASSERT(_numBits >= 1 && _numBits <= 64 + , "_numBits (%d) must be between 1-64." + , _numBits + ); + + const uint32_t idxB = (_pos >> 6); + const uint32_t shiftB = (_pos & 63); + + if (shiftB + _numBits <= 64) + { + const uint64_t mask = UINT64_MAX >> (64-_numBits); + _ptr[idxB] = replaceBits(_ptr[idxB], shiftB, mask, _bits); + + return; + } + + const uint32_t end = (_pos + _numBits); + const uint32_t idxE = ((end - 1) >> 6); + const uint32_t shiftE = (end & 63); + + const uint64_t maskB = UINT64_MAX >> shiftB; + _ptr[idxB] = replaceBits(_ptr[idxB], shiftB, maskB, _bits); + + const uint64_t bitsE = _bits >> (64 - shiftB); + const uint64_t maskE = UINT64_MAX >> (64 - shiftE); + _ptr[idxE] = replaceBits(_ptr[idxE], 0, maskE, bitsE); + } + + inline uint64_t bitSet_getBits(uint64_t* _ptr, uint32_t _pos, uint32_t _numBits) + { + BX_ASSERT(_numBits >= 1 && _numBits <= 64 + , "_numBits (%d) must be between 1-64." + , _numBits + ); + + const uint32_t idxB = (_pos >> 6); + const uint32_t shiftB = (_pos & 63); + + if (shiftB + _numBits <= 64) + { + const uint64_t mask = UINT64_MAX >> (64-_numBits); + return extractBits(_ptr[idxB], shiftB, mask); + } + + const uint32_t end = (_pos + _numBits); + const uint32_t idxE = ((end - 1) >> 6); + const uint32_t shiftE = (end & 63); + + const uint64_t maskB = UINT64_MAX >> shiftB; + const uint64_t bitsB = extractBits(_ptr[idxB], shiftB, maskB); + + const uint64_t maskE = UINT64_MAX >> (64 - shiftE); + const uint64_t bitsE = extractBits(_ptr[idxE], 0, maskE); + + return (bitsE << (64-shiftB) ) | bitsB; + } + + inline void copyBits(uint64_t* _ptr, uint32_t _dstBit, uint32_t _srcBit, uint32_t _numBits) + { + const uint64_t bits = bitSet_getBits(_ptr, _srcBit, _numBits); + bitSet_setBits(_ptr, _dstBit, bits, _numBits); + } + + inline void bitSetBlit(uint64_t* _ptr, uint32_t _dstBit, uint32_t _srcBit, uint32_t _numBits) + { + if (_dstBit == _srcBit + || 0 == _numBits) + { + return; + } + + if (_numBits <= 64) + { + copyBits(_ptr, _dstBit, _srcBit, _numBits); + } + else if (_numBits <= 128) + { + copyBits(_ptr, _dstBit, _srcBit, 64); + + const uint32_t numBits = _numBits - 64; + + if (0 < numBits) + { + copyBits(_ptr, _dstBit + 64, _srcBit + 64, numBits); + } + } + else if ( (_dstBit&7) == (_srcBit&7) ) + { + const uint32_t dstByte = alignUp(_dstBit, 8)>>3; + const uint32_t srcByte = alignUp(_srcBit, 8)>>3; + const uint32_t numPreBits = (dstByte<<3) - _dstBit; + const uint32_t tmp0 = _numBits - numPreBits; + const uint32_t numPostBits = tmp0&7; + const uint32_t numBytes = tmp0>>3; + uint8_t* ptr = (uint8_t*)_ptr; + + if (0 < numPreBits) + { + copyBits(_ptr, _dstBit, _srcBit, numPreBits); + } + + bx::memMove(ptr + dstByte, ptr + srcByte, numBytes); + + if (0 < numPostBits) + { + copyBits(_ptr, (dstByte + numBytes)<<3, (srcByte + numBytes)<<3, numPostBits); + } + } + else + { + uint32_t idxB = alignUp(_dstBit, 64)>>6; + const uint32_t numPreBits = (idxB<<6) - _dstBit; + + uint32_t srcBit = _srcBit; + + if (0 < numPreBits) + { + copyBits(_ptr, _dstBit, srcBit, numPreBits); + srcBit += numPreBits; + } + + uint32_t numBits = _numBits - numPreBits; + + for (; numBits >= 64; numBits -= 64, srcBit += 64) + { + const uint64_t bits = bitSet_getBits(_ptr, srcBit, 64); + _ptr[idxB++] = bits; + } + + if (0 < numBits) + { + copyBits(_ptr, idxB<<6, srcBit, numBits); + } + } + } + + template + inline uint32_t bitSetFindT(const uint64_t* _ptr, uint32_t _beginBit, uint32_t _endBit, uint32_t _numSteps) + { + const Ty* bits = (const Ty*)_ptr; + constexpr uint32_t numBitsPerStep = sizeof(Ty)*8; + const uint32_t numBits = _numSteps*numBitsPerStep; + + for (uint32_t ii = _beginBit/numBitsPerStep, end = alignUp(_endBit, numBitsPerStep)/numBitsPerStep; ii < end; ++ii) + { + if (bits[ii] != Ty(0) ) + { + continue; + } + + uint32_t idx = ii*numBitsPerStep; + uint32_t num = min(idx + numBitsPerStep, _endBit) - idx; + + if (idx != _beginBit) + { + const Ty test = bits[idx/numBitsPerStep - 1]; + const uint32_t newIdx = max(idx - countLeadingZeros(test), _beginBit); + + num += idx - newIdx; + idx = newIdx; + } + + for (++ii; ii < end; ++ii) + { + if (num >= numBits) + { + return idx; + } + + const Ty test = bits[ii]; + const uint32_t pos = ii * numBitsPerStep; + num += min(pos + countTrailingZeros(test), _endBit) - pos; + + if (test != Ty(0) ) + { + break; + } + } + + if (num >= numBits) + { + return idx; + } + } + + return _endBit; + } + + inline uint32_t bitSetFindUpto14(const uint64_t* _ptr, uint32_t _beginBit, uint32_t _endBit, uint32_t _numBits) + { + const uint8_t* bits = (const uint8_t*)_ptr; + + const uint8_t beginMask = uint8_t(~(0xff << (_beginBit & 7) ) ); + + uint32_t lz = 0; + + for (uint32_t begin = _beginBit / 8, end = alignUp(_endBit, 8) / 8, ii = begin; ii < end; ++ii) + { + uint8_t test = bits[ii]; + test |= ii == begin ? beginMask : 0; + + if (0xff == test) + { + continue; + } + + const uint32_t pos = ii * 8; + const uint32_t tz = min(pos + countTrailingZeros(test), _endBit) - pos; + + if (lz + tz >= _numBits) + { + return pos - lz; + } + + lz = countLeadingZeros(test); + + const uint32_t idx = pos + 8; + const uint32_t newIdx = max(idx - lz, _beginBit); + lz = min(idx, _endBit) - newIdx; + + if (lz >= _numBits) + { + return newIdx; + } + } + + return _endBit; + } + + inline uint32_t bitSetFindUpto6(const uint64_t* _ptr, uint32_t _beginBit, uint32_t _endBit, uint32_t _numBits) + { + const uint8_t* bits = (const uint8_t*)_ptr; + + const uint8_t beginMask = uint8_t(~(0xff << (_beginBit & 7) ) ); + const uint8_t endMask = uint8_t(~(0xff >> ( ( (8-(_endBit & 7) ) & 7) ) ) ); + const uint8_t mask = (1<<_numBits) - 1; + + for (uint32_t begin = _beginBit / 8, end = alignUp(_endBit, 8) / 8, ii = begin; ii < end; ++ii) + { + uint16_t test = bits[ii]; + test |= ii == begin ? beginMask : 0; + test |= ii == end - 1 ? endMask : 0; + test |= 0xff00; + + if (0xffff == test) + { + continue; + } + + for (uint32_t pos = ii*8, posEnd = pos+7; pos < posEnd; ++pos) + { + const uint8_t tz = countTrailingZeros(test^0xff); + test = int16_t(test) >> tz; + + pos += tz; + + if (0 == (test & mask) ) + { + return pos; + } + + test = int16_t(test) >> 1; + } + } + + return _endBit; + } + + inline uint32_t bitSetFind(const uint64_t* _ptr, uint32_t _beginBit, uint32_t _endBit, uint32_t _numBits) + { + if (_numBits >= 127) + { + const uint32_t idx = bitSetFindT(_ptr, _beginBit, _endBit, (_numBits+63)>>6); + if (idx != _endBit) + { + return idx; + } + } + + if (_numBits >= 63) + { + const uint32_t idx = bitSetFindT(_ptr, _beginBit, _endBit, (_numBits+31)>>5); + if (idx != _endBit) + { + return idx; + } + } + + if (_numBits >= 128) + { + return kInvalid; + } + + if (_numBits >= 31) + { + const uint32_t idx = bitSetFindT(_ptr, _beginBit, _endBit, (_numBits+15)>>4); + if (idx != _endBit) + { + return idx; + } + } + + if (_numBits >= 64) + { + return kInvalid; + } + + uint32_t idx = bitSetFindT(_ptr, _beginBit, _endBit, (_numBits+7)>>3); + if (idx != _endBit) + { + return idx; + } + + if (_numBits < 15) + { + idx = bitSetFindUpto14(_ptr, _beginBit, _endBit, _numBits); + + if (idx != _endBit) + { + return idx; + } + + if (_numBits < 7) + { + idx = bitSetFindUpto6(_ptr, _beginBit, _endBit, _numBits); + + if (idx != _endBit) + { + return idx; + } + } + } + + return kInvalid; + } + + uint64_t BitArrayViewBase::get(uint32_t _beginBit, uint32_t _endBit) const + { + BX_ASSERT(true + && _beginBit <= m_count + && _endBit <= m_count + && _beginBit <= _endBit + , "Incorrect usage `%s`. _beginBit %d, _endBit %d, m_count %d [%d%d%d]" + , BX_FUNCTION + , _beginBit + , _endBit + , m_count + , _beginBit <= m_count + , _endBit <= m_count + , _beginBit <= _endBit + ); + return bitSet_getBits(m_ptr, _beginBit, _endBit-_beginBit); + } + + uint32_t BitArrayViewBase::findRange(uint32_t _beginBit, uint32_t _endBit, uint32_t _numBits) const + { + return bitSetFind(m_ptr, _beginBit, _endBit, _numBits); + } + + uint32_t BitArrayViewBase::findSet(Element _element) const + { + uint32_t from = 0; + uint32_t mask = 0; + const uint32_t num = m_sizeInBytes/8; + + if (kLast == _element) + { + from = num; + mask = UINT32_MAX; + } + + for (uint32_t ii = 0; ii < num; ++ii) + { + const uint32_t idx = ( (~from + 1) + ii) ^ mask; + const uint64_t word = m_ptr[idx]; + + if (0 == word) + { + continue; + } + + if (kLast == _element) + { + return idx*64 + findLastSet(word) - 1; + } + + return idx*64 + findFirstSet(word) - 1; + } + + return kInvalid; + } + + uint32_t BitArrayViewBase::countBits(uint32_t _beginBit, uint32_t _endBit) const + { + BX_ASSERT(true + && _beginBit <= m_count + && _endBit <= m_count + && _beginBit <= _endBit + , "Incorrect usage `%s`. _beginBit %d, _endBit %d, m_count %d [%d%d%d]" + , BX_FUNCTION + , _beginBit + , _endBit + , m_count + , _beginBit <= m_count + , _endBit <= m_count + , _beginBit <= _endBit + ); + return bitSetCountBits(m_ptr, _beginBit, _endBit); + } + + bool BitArrayViewBase::testAny(uint32_t _beginBit, uint32_t _endBit) const + { + BX_ASSERT(true + && _beginBit <= m_count + && _endBit <= m_count + && _beginBit <= _endBit + , "Incorrect usage `%s`. _beginBit %d, _endBit %d, m_count %d [%d%d%d]" + , BX_FUNCTION + , _beginBit + , _endBit + , m_count + , _beginBit <= m_count + , _endBit <= m_count + , _beginBit <= _endBit + ); + return bitSetTestAny(m_ptr, _beginBit, _endBit); + } + + bool BitArrayViewBase::testAll(uint32_t _beginBit, uint32_t _endBit) const + { + BX_ASSERT(true + && _beginBit <= m_count + && _endBit <= m_count + && _beginBit <= _endBit + , "Incorrect usage `%s`. _beginBit %d, _endBit %d, m_count %d [%d%d%d]" + , BX_FUNCTION + , _beginBit + , _endBit + , m_count + , _beginBit <= m_count + , _endBit <= m_count + , _beginBit <= _endBit + ); + return bitSetTestAll(m_ptr, _beginBit, _endBit); + } + + bool BitArrayViewBase::testNone(uint32_t _beginBit, uint32_t _endBit) const + { + BX_ASSERT(true + && _beginBit <= m_count + && _endBit <= m_count + && _beginBit <= _endBit + , "Incorrect usage `%s`. _beginBit %d, _endBit %d, m_count %d [%d%d%d]" + , BX_FUNCTION + , _beginBit + , _endBit + , m_count + , _beginBit <= m_count + , _endBit <= m_count + , _beginBit <= _endBit + ); + return bitSetTestNone(m_ptr, _beginBit, _endBit); + } + + void MutableBitArrayView::set(uint32_t _beginBit, uint32_t _endBit, bool _value) + { + BX_ASSERT(true + && _beginBit <= m_count + && _endBit <= m_count + && _beginBit <= _endBit + , "Incorrect usage `%s`. _beginBit %d, _endBit %d, m_count %d [%d%d%d]" + , BX_FUNCTION + , _beginBit + , _endBit + , m_count + , _beginBit <= m_count + , _endBit <= m_count + , _beginBit <= _endBit + ); + bitSetSet(m_ptr, _beginBit, _endBit, _value); + } + + void MutableBitArrayView::set(uint32_t _beginBit, uint32_t _endBit, uint64_t _value) + { + BX_ASSERT(true + && _beginBit <= m_count + && _endBit <= m_count + && _beginBit <= _endBit + , "Incorrect usage `%s`. _beginBit %d, _endBit %d, m_count %d [%d%d%d]" + , BX_FUNCTION + , _beginBit + , _endBit + , m_count + , _beginBit <= m_count + , _endBit <= m_count + , _beginBit <= _endBit + ); + return bitSet_setBits(m_ptr, _beginBit, _value, _endBit-_beginBit); + } + + void MutableBitArrayView::blit(uint32_t _dstBit, uint32_t _srcBit, uint32_t _numBits) + { + bitSetBlit(m_ptr, _dstBit, _srcBit, _numBits); + } + +} // namespace bx diff --git a/tests/bitarray_test.cpp b/tests/bitarray_test.cpp new file mode 100644 index 0000000..94936a4 --- /dev/null +++ b/tests/bitarray_test.cpp @@ -0,0 +1,876 @@ +/* + * Copyright 2010-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bx#license-bsd-2-clause + */ + +#include "test.h" + +#include +#include +#include + +static void testRange(bx::MutableBitArrayView& _bs, uint32_t _beginBit, uint32_t _endBit) +{ + _bs.clear(); + _bs.set(_beginBit, _endBit, true); + + const uint32_t beginBitMinus1 = bx::max(_beginBit-1, 0); + const uint32_t endBitPlus1 = bx::min(_endBit+1, _bs.getCount() ); + + if (beginBitMinus1 != _beginBit) + { + REQUIRE(!_bs.get(beginBitMinus1) ); + } + + if (endBitPlus1 != _endBit) + { + REQUIRE(!_bs.get(endBitPlus1) ); + } + + REQUIRE(_bs.testAll(_beginBit, _endBit) ); + + const uint32_t bitCount = _endBit - _beginBit; + + REQUIRE(bitCount == _bs.countBits(beginBitMinus1, endBitPlus1) ); + REQUIRE(bitCount == _bs.countBits() ); +} + +TEST_CASE("BitArray", "[container]") +{ + uint64_t bits[64]; + bx::MutableBitArrayView bs(bits, sizeof(bits) ); + REQUIRE(bs.getCapacity() == 4096); + + bs.clear(true); + + REQUIRE(!bs.get(0) ); + + REQUIRE(0 == bs.find(128) ); + + bs.set(0, true); + REQUIRE(bs.get(0) ); + + bs.set(0, false); + REQUIRE(!bs.get(0) ); + + REQUIRE(bs.testNone() ); + REQUIRE(!bs.testAny() ); + + REQUIRE(bs.testNone(1, 5) ); + + bs.set(1, 5, true); + + REQUIRE(4 == bs.countBits() ); + REQUIRE(4 == bs.countBits(1, 5) ); + + bs.set(4095, true); + REQUIRE(5 == bs.countBits() ); + REQUIRE(4 == bs.countBits(1, 5) ); + + REQUIRE(5 == bs.find(32) ); + + REQUIRE(bs.testNone(1, 1) ); + REQUIRE(bs.testNone(2, 2) ); + REQUIRE(bs.testNone(0, 0) ); + REQUIRE(!bs.testAny(0, 0) ); + + REQUIRE(bs.testAll(1, 4) ); + REQUIRE(bs.testAny(1, 5) ); + + REQUIRE(bs.testNone(6, 63) ); + REQUIRE(!bs.testAny(6, 63) ); + + REQUIRE(bs.testAny(0, 63) ); + REQUIRE(bs.testNone(61, 75) ); + + bs.set(61, 75, true); + REQUIRE(75 == bs.find(128) ); + + REQUIRE(bs.testNone(60, 60) ); + REQUIRE(!bs.testAny(60, 60) ); + + REQUIRE(bs.testAll(61, 75) ); + REQUIRE(bs.testAny(61, 75) ); + + REQUIRE(bs.testNone(76, 127) ); + REQUIRE(!bs.testAny(76, 127) ); + + REQUIRE(bs.testAny(0, 127) ); + + bs.clear(); + REQUIRE(bs.testNone() ); + REQUIRE(!bs.testAny() ); + + uint32_t idx = bs.find(8); + REQUIRE(0 == idx); + bs.set(0, 7, true); + + idx = bs.find(512); + REQUIRE(7 == idx); + bs.set(7, 519, true); + + idx = bs.find(16); + REQUIRE(519 == idx); + bs.set(519, 535, true); + + bs.clear(); + bs.set(32, 64, true); + bs.blit(1, 32, 30); + REQUIRE(bs.testAll(8, 23) ); + + bs.clear(); + bs.set(64, 255, true); + bs.blit(1, 65, 128); + REQUIRE(bs.testAll(8, 23) ); + + bs.clear(); + bs.set(64, 192, true); + bs.blit(3, 65, 127); + REQUIRE(bs.testAll(3, 130) ); + + testRange(bs, 0, 65); + testRange(bs, 63, 65); +} + +TEST_CASE("BitArray-argument-validation", "[container]") +{ + uint64_t bits[1]; + bx::MutableBitArrayView bs(bits, sizeof(bits), 1); + + REQUIRE_ASSERTS(bs.find(0, 0, 1) ); // Invalid range. + REQUIRE_ASSERTS(bs.find(0, 8, 1) ); // Invalid range. + REQUIRE_ASSERTS(bs.find(1, 2, 1) ); // Invalid range. + REQUIRE_ASSERTS(bs.find(0, 1, 0) ); // Invalid number of bits. + REQUIRE_ASSERTS(bs.find(0, 1, 2) ); // Number of bits larger than range. + REQUIRE_ASSERTS(bs.find(1, 0, 1) ); // Range invalid / backwards. +} + +TEST_CASE("BitArray-6-bits", "[container]") +{ + constexpr uint32_t numBits = 64; + + uint64_t bits[1]; + bx::MutableBitArrayView bs(bits, sizeof(bits), numBits); + REQUIRE(bs.getCapacity() == 64); + + bs.set(0, 64, (UINT64_MAX << 16) | 0b1100000110101000ul); + + REQUIRE(9 == bs.find(0, 63, 4) ); + REQUIRE(9 == bs.find(0, 63, 5) ); + REQUIRE(bx::kInvalid == bs.find(0, 63, 6) ); + REQUIRE(bx::kInvalid == bs.find(0, 63, 7) ); +} + +TEST_CASE("BitArray-7-bits", "[container]") +{ + const uint32_t numBits = 7; + + uint64_t bits[1]; + bx::MutableBitArrayView bs(bits, sizeof(bits), numBits); + REQUIRE(bs.getCapacity() == 64); + + bs.clear(true); + + REQUIRE(!bs.get(3) ); + bs.set(3, true); + REQUIRE(bs.get(3) ); + + REQUIRE(!bs.testAll(0, numBits) ); + REQUIRE(!bs.testNone(0, numBits) ); + REQUIRE(bs.testAny(0, numBits) ); + REQUIRE(1 == bs.countBits(0, numBits) ); + + REQUIRE(!bs.testAll(0, 2) ); + REQUIRE(bs.testNone(0, 2) ); + REQUIRE(!bs.testAny(0, 2) ); + + bs.clear(); + REQUIRE(!bs.get(3) ); + REQUIRE(0 == bs.countBits(0, numBits) ); + + bs.set(3, 7, true); + REQUIRE(4 == bs.countBits(0, numBits) ); + + bs.set(0, numBits, true); + REQUIRE(!bs.testNone(0, numBits) ); + REQUIRE(bs.testAll(0, numBits) ); + + bs.set(0, numBits, false); + REQUIRE(bs.testNone(0, numBits) ); + REQUIRE(!bs.testAll(0, numBits) ); + + testRange(bs, 0, 3); + testRange(bs, 1, 3); + testRange(bs, 2, 5); + testRange(bs, 3, 4); + testRange(bs, 4, 7); + testRange(bs, 5, 6); + testRange(bs, 6, 7); +} + +TEST_CASE("BitArray-31-bits", "[container]") +{ + constexpr uint32_t numBits = 31; + + uint64_t bits[1]; + bx::MutableBitArrayView bs(bits, sizeof(bits), numBits); + REQUIRE(bs.getCapacity() == 64); + + bs.clear(true); + + REQUIRE(!bs.get(13) ); + bs.set(13, true); + REQUIRE(bs.get(13) ); + + REQUIRE(!bs.testAll(0, numBits) ); + REQUIRE(!bs.testNone(0, numBits) ); + REQUIRE(bs.testAny(0, numBits) ); + REQUIRE(1 == bs.countBits(0, numBits) ); + + REQUIRE(!bs.testAll(0, 12) ); + REQUIRE(bs.testNone(0, 12) ); + REQUIRE(!bs.testAny(0, 12) ); + + bs.clear(); + REQUIRE(!bs.get(13) ); + REQUIRE(0 == bs.countBits(0, numBits) ); + + bs.set(4, 8, true); + REQUIRE(4 == bs.countBits(0, numBits) ); + + bs.set(0, numBits, true); + REQUIRE(!bs.testNone(0, numBits) ); + REQUIRE(bs.testAll(0, numBits) ); + + bs.set(0, numBits, false); + REQUIRE(bs.testNone(0, numBits) ); + REQUIRE(!bs.testAll(0, numBits) ); + + bs.set(13, 20, true); + REQUIRE(bs.testAll(13, 20) ); + + testRange(bs, 0, 3); + testRange(bs, 1, 3); + testRange(bs, 2, 5); + testRange(bs, 3, 4); + testRange(bs, 4, 7); + testRange(bs, 5, 6); + testRange(bs, 6, 10); +} + +static void findWithPattern(bx::MutableBitArrayView& _bs, uint8_t _pattern, uint32_t _numBits) +{ + BX_ASSERT(0 == _bs.getCount() % 8, "BitArray lenght must be multiple of 8."); + + uint8_t* bytes = (uint8_t*)_bs.getPtr(); + bx::memSet(bytes, _pattern, _bs.getCount()/8); + + const uint8_t bitCount = bx::countBits(_pattern); + const uint32_t numEmptyBits = _bs.getCount() - (_bs.getCount() / 8 * bitCount); + + for (uint32_t ii = 0; ii < numEmptyBits; ii += _numBits) + { + const uint32_t idx = _bs.find(_numBits); + REQUIRE(idx != bx::kInvalid); + + _bs.set(idx, idx+_numBits, true); + } + + REQUIRE(_bs.testAll() ); +} + +TEST_CASE("BitArray-find-unset-bit", "[container]") +{ + uint64_t bits[8]; + bx::MutableBitArrayView bs(bits, sizeof(bits) ); + REQUIRE(bs.getCapacity() == 512); + + for (uint32_t ii = 0, num = bs.getCount(); ii < num; ++ii) + { + bs.set(0, num, true); + bs.set(ii, false); + REQUIRE(ii == bs.find(1) ); + } +} + +TEST_CASE("BitArray-find-with-pattern", "[container]") +{ + uint64_t bits[8]; + bx::MutableBitArrayView bs(bits, sizeof(bits) ); + REQUIRE(bs.getCapacity() == 512); + + findWithPattern(bs, 0x55, 1); + findWithPattern(bs, 0x81, 1); + findWithPattern(bs, 0x81, 2); + findWithPattern(bs, 0x81, 3); + findWithPattern(bs, 0x81, 6); + findWithPattern(bs, 0x88, 3); + findWithPattern(bs, 0x99, 2); + findWithPattern(bs, 0xaa, 1); + findWithPattern(bs, 0xc3, 1); + findWithPattern(bs, 0xc3, 2); + findWithPattern(bs, 0xc3, 4); + findWithPattern(bs, 0xe7, 1); + findWithPattern(bs, 0xe7, 2); + + for (uint32_t ii = 0; ii < 256; ++ii) + { + findWithPattern(bs, uint8_t(ii), 1); + } +} + +static void testGet(bx::MutableBitArrayView& _bs, uint32_t _pos, uint32_t _numBits) +{ + _bs.set(_pos, _pos+_numBits, true); + REQUIRE(_numBits == _bs.countBits(0, _bs.getCount() ) ); + REQUIRE(UINT64_MAX >> (64 - _numBits) == _bs.get(_pos, _pos + _numBits) ); + _bs.clear(); +} + +TEST_CASE("BitArray-get", "[container]") +{ + uint64_t bits[4]; + bx::MutableBitArrayView bs(bits, sizeof(bits) ); + REQUIRE(bs.getCapacity() == 256); + + bs.clear(true); + + testGet(bs, 0, 5); + testGet(bs, 1, 3); + testGet(bs, 0, 63); + testGet(bs, 0, 64); + testGet(bs, 1, 63); + testGet(bs, 1, 64); + testGet(bs, 62, 5); + testGet(bs, 127, 3); + testGet(bs, 250, 6); + testGet(bs, 254, 2); +} + +static void testSet(bx::MutableBitArrayView& _bs, uint32_t _pos, uint64_t _value, uint32_t _numBits) +{ + _bs.set(_pos, _pos+_numBits, _value); + + REQUIRE(_value == _bs.get(_pos, _pos + _numBits) ); + _bs.clear(); +} + +TEST_CASE("BitArray-set", "[container]") +{ + uint64_t bits[4]; + bx::MutableBitArrayView bs(bits, sizeof(bits) ); + REQUIRE(bs.getCapacity() == 256); + + bs.clear(true); + + testSet(bs, 0, 1, 32); + + testSet(bs, 0, 16, 5); + testSet(bs, 1, 7, 3); + testSet(bs, 1, 32, 64); + testSet(bs, 62, 6, 5); + testSet(bs, 127, 1, 3); + testSet(bs, 60, 0xaa, 8); +} + +TEST_CASE("BitArray-tight-pack", "[container]") +{ + uint64_t bits[2]; + bx::MutableBitArrayView bs(bits, sizeof(bits) ); + REQUIRE(bs.getCapacity() == 128); + + bs.clear(true); + bs.set(0, 80, true); + + REQUIRE(bx::kInvalid != bs.find(48) ); + + bs.clear(true); + bs.set(32, 128, true); + + REQUIRE(bx::kInvalid != bs.find(32) ); +} + +TEST_CASE("BitArray-set-all-bits-one-by-one", "[container]") +{ + bx::DefaultAllocator crtAllocator; + + constexpr uint32_t kCount = 64; + + bx::BitArray bs(&crtAllocator); + bs.setCount(kCount); + + REQUIRE(kCount == bs.getCount() ); + + bs.clear(true); + + for (uint32_t ii = 0, num = bs.getCount(); ii < num; ++ii) + { + uint32_t idx = bs.find(1); + REQUIRE(bx::kInvalid != idx); + + bs.set(idx, true); + } +} + +TEST_CASE("BitArray-resize", "[container]") +{ + bx::DefaultAllocator allocator; + bx::BitArray array(&allocator); + + array.setCapacity(1024); + REQUIRE(1024 == array.getCapacity() ); + REQUIRE(0 == array.getCount() ); + + array.setCount(2048); + REQUIRE(2048 == array.getCount() ); + REQUIRE(2048 == array.getCapacity() ); + + array.set(0, 2048, true); + REQUIRE(array.testAll() ); + + array.clear(true); + REQUIRE(array.testNone() ); + + testGet(array, 0, 5); + testGet(array, 1, 3); + testGet(array, 0, 63); + testGet(array, 0, 64); + testGet(array, 1, 63); + testGet(array, 1, 64); + testGet(array, 62, 5); + testGet(array, 127, 3); + testGet(array, 250, 6); + testGet(array, 254, 2); +} + +TEST_CASE("BitArray-findSet", "[container]") +{ + bx::DefaultAllocator allocator; + bx::BitArray array(&allocator); + + constexpr uint32_t kCapacity = 1024; + array.setCount(kCapacity); + + for (uint32_t ii = 0; ii < kCapacity/3; ++ii) + { + constexpr uint32_t kFirstIdx = 32; + constexpr uint32_t kLastIdx = kCapacity-kFirstIdx; + + array.clear(); + + array.set(kFirstIdx, true); + REQUIRE(kFirstIdx == array.findSet(bx::kFirst) ); + REQUIRE(kFirstIdx == array.findSet(bx::kLast) ); + + array.set(kLastIdx, true); + REQUIRE(kFirstIdx == array.findSet(bx::kFirst) ); + REQUIRE(kLastIdx == array.findSet(bx::kLast) ); + + array.set(kFirstIdx, false); + REQUIRE(kLastIdx == array.findSet(bx::kFirst) ); + REQUIRE(kLastIdx == array.findSet(bx::kLast) ); + } +} + +TEST_CASE("BitArray-findSet-empty", "[container]") +{ + bx::DefaultAllocator allocator; + bx::BitArray array(&allocator); + + array.setCount(128); + array.clear(true); + + // All zeros — findSet should return kInvalid. + REQUIRE(bx::kInvalid == array.findSet(bx::kFirst) ); + REQUIRE(bx::kInvalid == array.findSet(bx::kLast) ); + + // Set one bit, verify findSet finds it. + array.set(64, true); + REQUIRE(64 == array.findSet(bx::kFirst) ); + REQUIRE(64 == array.findSet(bx::kLast) ); +} + +TEST_CASE("FixedBitArrayT", "[container]") +{ + bx::FixedBitArrayT<128> ba; + REQUIRE(0 == ba.getCount() ); + REQUIRE(128 == ba.getCapacity() ); + + ba.setCount(100); + REQUIRE(100 == ba.getCount() ); + + ba.clear(true); + REQUIRE(ba.testNone() ); + + // Set individual bits and verify. + ba.set(0, true); + ba.set(50, true); + ba.set(99, true); + + REQUIRE(ba.get(0) ); + REQUIRE(ba.get(50) ); + REQUIRE(ba.get(99) ); + REQUIRE(!ba.get(1) ); + REQUIRE(!ba.get(51) ); + + REQUIRE(3 == ba.countBits() ); + + // Find unset run after setting a range. + ba.clear(true); + REQUIRE(0 == ba.find(1) ); + + ba.set(0, 64, true); + REQUIRE(64 == ba.find(1) ); + + // Set range and verify. + ba.clear(true); + ba.set(10, 20, true); + REQUIRE(ba.testAll(10, 20) ); + REQUIRE(10 == ba.countBits() ); + + // testAny/testNone/testAll full array. + ba.clear(true); + REQUIRE(ba.testNone() ); + REQUIRE(!ba.testAny() ); + REQUIRE(!ba.testAll() ); + + ba.set(0, 100, true); + REQUIRE(ba.testAll() ); + REQUIRE(ba.testAny() ); + REQUIRE(!ba.testNone() ); +} + +TEST_CASE("BitArray-copy-ctor", "[container]") +{ + bx::DefaultAllocator allocator; + bx::BitArray src(&allocator); + src.setCount(256); + src.clear(true); + src.set(0, 64, true); + src.set(200, true); + + bx::BitArray dst(src); + + REQUIRE(dst.getCount() == src.getCount() ); + REQUIRE(dst.countBits() == src.countBits() ); + REQUIRE(dst.testAll(0, 64) ); + REQUIRE(dst.get(200) ); + REQUIRE(!dst.get(100) ); + + // Verify they are independent copies. + dst.set(100, true); + REQUIRE(!src.get(100) ); + REQUIRE(dst.get(100) ); +} + +TEST_CASE("BitArray-copy-assignment", "[container]") +{ + bx::DefaultAllocator allocator; + + bx::BitArray src(&allocator); + src.setCount(128); + src.clear(true); + src.set(0, 32, true); + + bx::BitArray dst(&allocator); + dst.setCount(64); + dst.clear(true); + + dst = src; + + REQUIRE(128 == dst.getCount() ); + REQUIRE(dst.testAll(0, 32) ); + REQUIRE(dst.testNone(32, 128) ); + + // Verify independence. + src.set(64, true); + REQUIRE(!dst.get(64) ); +} + +TEST_CASE("BitArray-empty-predicates", "[container]") +{ + bx::DefaultAllocator allocator; + + // A default-constructed BitArray owns no storage. Range predicates over an + // empty range must answer without touching the (NULL) buffer. + const bx::BitArray ba(&allocator); + + REQUIRE(0 == ba.getCount() ); + REQUIRE(0 == ba.countBits() ); + REQUIRE(!ba.testAny() ); + REQUIRE(ba.testAll() ); + REQUIRE(ba.testNone() ); +} + +TEST_CASE("BitArray-copy-assignment from empty", "[container]") +{ + bx::DefaultAllocator allocator; + + // Assigning an empty array releases the destination's storage. The + // destination must end up empty rather than keeping a count that refers + // to the freed block. + const bx::BitArray src(&allocator); + + bx::BitArray dst(&allocator); + dst.setCount(128); + dst.clear(true); + dst.set(0, 128, true); + + dst = src; + + REQUIRE(0 == dst.getCount() ); + REQUIRE(0 == dst.countBits() ); + + // Destination must be reusable after the release. + dst.setCount(64); + dst.clear(true); + REQUIRE(64 == dst.getCount() ); + REQUIRE(dst.testNone() ); +} + +TEST_CASE("BitArray-move-assignment", "[container]") +{ + bx::DefaultAllocator allocator; + + bx::BitArray src(&allocator); + src.setCount(256); + src.clear(true); + src.set(0, 128, true); + + bx::BitArray dst(&allocator); + dst = bx::move(src); + + REQUIRE(256 == dst.getCount() ); + REQUIRE(dst.testAll(0, 128) ); + REQUIRE(dst.testNone(128, 256) ); + + // Source should be empty after move. + REQUIRE(0 == src.getCount() ); +} + +TEST_CASE("BitArray-construct-from-view", "[container]") +{ + uint64_t bits[2]; + bx::MutableBitArrayView view(bits, sizeof(bits), 100); + view.clear(true); + view.set(10, 50, true); + + bx::DefaultAllocator allocator; + bx::BitArray array(&allocator, view); + + REQUIRE(100 == array.getCount() ); + REQUIRE(array.testAll(10, 50) ); + REQUIRE(array.testNone(0, 10) ); + REQUIRE(array.testNone(50, 100) ); +} + +TEST_CASE("BitArray-set-from-view", "[container]") +{ + uint64_t bits[4]; + bx::MutableBitArrayView view(bits, sizeof(bits), 200); + view.clear(true); + view.set(0, 100, true); + + bx::DefaultAllocator allocator; + bx::BitArray array(&allocator); + + array.set(static_cast(view) ); + + REQUIRE(200 == array.getCount() ); + REQUIRE(array.testAll(0, 100) ); + REQUIRE(array.testNone(100, 200) ); +} + +TEST_CASE("BitArray-set-move", "[container]") +{ + bx::DefaultAllocator allocator; + + bx::BitArray src(&allocator); + src.setCount(512); + src.clear(true); + src.set(0, 256, true); + + bx::BitArray dst(&allocator); + dst.set(bx::move(src) ); + + REQUIRE(512 == dst.getCount() ); + REQUIRE(dst.testAll(0, 256) ); + REQUIRE(dst.testNone(256, 512) ); + + // Source should be empty after move. + REQUIRE(0 == src.getCount() ); +} + +TEST_CASE("MutableBitArrayView-reassign", "[container]") +{ + uint64_t bits1[1]; + uint64_t bits2[2]; + + bx::MutableBitArrayView view(bits1, sizeof(bits1), 64); + REQUIRE(64 == view.getCount() ); + + view.clear(true); + view.set(0, true); + view.set(63, true); + REQUIRE(view.get(0) ); + REQUIRE(view.get(63) ); + + // Reassign to second buffer. + view.set(bits2, 100); + REQUIRE(100 == view.getCount() ); + + bx::memSet(bits2, 0, sizeof(bits2) ); + view.set(99, true); + REQUIRE(view.get(99) ); + REQUIRE(!view.get(0) ); +} + +TEST_CASE("BitArray-getPtr-getTerm", "[container]") +{ + uint64_t bits[64]; + bx::MutableBitArrayView view(bits, sizeof(bits) ); + + REQUIRE(bits == view.getPtr() ); + REQUIRE(static_cast(bits + 64) == view.getTerm() ); + + const bx::MutableBitArrayView& constView = view; + REQUIRE(static_cast(bits) == constView.getPtr() ); + REQUIRE(static_cast(bits + 64) == constView.getTerm() ); +} + +TEST_CASE("BitArray-getAllocator", "[container]") +{ + bx::DefaultAllocator allocator; + bx::BitArray array(&allocator); + + REQUIRE(&allocator == array.getAllocator() ); +} + +TEST_CASE("BitArray-findSet-findClear-range", "[container]") +{ + bx::FixedBitArrayT<200> array; + array.setCount(200); + + REQUIRE(bx::kInvalid == array.findSet(0, 200) ); + REQUIRE(0 == array.findClear(0, 200) ); + + array.set(0, true); + array.set(63, true); + array.set(64, true); + array.set(130, true); + array.set(199, true); + + REQUIRE(0 == array.findSet(0, 200) ); + REQUIRE(63 == array.findSet(1, 200) ); + REQUIRE(64 == array.findSet(64, 200) ); + REQUIRE(130 == array.findSet(65, 200) ); + REQUIRE(199 == array.findSet(131, 200) ); + REQUIRE(bx::kInvalid == array.findSet(131, 199) ); + REQUIRE(bx::kInvalid == array.findSet(200, 200) ); + + REQUIRE(1 == array.findClear(0, 200) ); + REQUIRE(65 == array.findClear(63, 200) ); + REQUIRE(bx::kInvalid == array.findClear(199, 200) ); + + array.set(0, 200, true); + REQUIRE(bx::kInvalid == array.findClear(0, 200) ); + array.set(77, false); + REQUIRE(77 == array.findClear(0, 200) ); + REQUIRE(bx::kInvalid == array.findClear(78, 200) ); +} + +TEST_CASE("MippedBitArray-fixed", "[container]") +{ + bx::FixedMippedBitArrayT<4096> array; + REQUIRE(4096 == array.getCapacity() ); + array.setCount(4096); + + REQUIRE(0 == array.findClear(0, 4096) ); + + array.set(0, 4096, true); + REQUIRE(bx::kInvalid == array.findClear(0, 4096) ); + REQUIRE(array.testAll() ); + + array.set(4000, false); + REQUIRE(4000 == array.findClear(0, 4096) ); + REQUIRE(bx::kInvalid == array.findClear(4001, 4096) ); + + array.set(64, 128, false); + REQUIRE(64 == array.findClear(0, 4096) ); + REQUIRE(100 == array.findClear(100, 4096) ); + REQUIRE(4000 == array.findClear(128, 4096) ); + + array.set(64, 128, true); + REQUIRE(4000 == array.findClear(0, 4096) ); + + array.set(4000, true); + REQUIRE(bx::kInvalid == array.findClear(0, 4096) ); + + array.set(200, 232, uint64_t(0) ); + REQUIRE(200 == array.findClear(0, 4096) ); + REQUIRE(bx::kInvalid == array.findClear(232, 4096) ); + array.set(200, 232, true); + + array.set(0, 64, false); + array.blit(2048, 0, 64); + REQUIRE(2048 == array.findClear(64, 4096) ); + array.set(0, 64, true); + array.set(2048, 2112, true); + REQUIRE(bx::kInvalid == array.findClear(0, 4096) ); + + array.clear(); + REQUIRE(0 == array.findClear(0, 4096) ); + REQUIRE(array.testNone() ); + + bx::FixedMippedBitArrayT<65> odd; + odd.setCount(65); + odd.set(0, 65, true); + REQUIRE(bx::kInvalid == odd.findClear(0, 65) ); + odd.set(64, false); + REQUIRE(64 == odd.findClear(0, 65) ); + odd.set(3, false); + REQUIRE(3 == odd.findClear(0, 65) ); + REQUIRE(64 == odd.findClear(4, 65) ); +} + +TEST_CASE("MippedBitArray-dynamic", "[container]") +{ + bx::DefaultAllocator allocator; + bx::MippedBitArray array(&allocator); + + REQUIRE(0 == array.getCapacity() ); + REQUIRE(bx::kInvalid == array.findClear(0, 0) ); + + array.setCount(100); + array.set(0, 100, true); + REQUIRE(bx::kInvalid == array.findClear(0, 100) ); + + array.setCount(200); + REQUIRE(100 == array.findClear(0, 200) ); + array.set(100, 200, true); + REQUIRE(bx::kInvalid == array.findClear(0, 200) ); + + array.setCount(5000); + REQUIRE(200 == array.findClear(0, 5000) ); + array.set(200, 5000, true); + REQUIRE(bx::kInvalid == array.findClear(0, 5000) ); + REQUIRE(array.testAll() ); + + array.set(4999, false); + REQUIRE(4999 == array.findClear(0, 5000) ); + array.set(63, false); + REQUIRE(63 == array.findClear(0, 5000) ); + REQUIRE(4999 == array.findClear(64, 5000) ); + + array.setCapacity(128); + REQUIRE(128 == array.getCapacity() ); + array.setCount(128); + REQUIRE(63 == array.findClear(0, 128) ); + REQUIRE(bx::kInvalid == array.findClear(64, 128) ); + + array.setCount(65); + array.setCount(300); + REQUIRE(array.get(64) ); + REQUIRE(65 == array.findClear(64, 300) ); + REQUIRE(bx::kInvalid == array.findClear(64, 65) ); +} diff --git a/tests/handle_test.cpp b/tests/handle_test.cpp index afce746..cc41d3c 100644 --- a/tests/handle_test.cpp +++ b/tests/handle_test.cpp @@ -191,3 +191,288 @@ TEST_CASE("HandleHashTable", "") REQUIRE(ok); } } + +TEST_CASE("HandleAlloc2", "[container]") +{ + constexpr uint16_t kMaxHandles = 1024; + + bx::HandleAlloc2T handleAlloc; + + REQUIRE(0 == handleAlloc.getNumHandles() ); + REQUIRE(kMaxHandles == handleAlloc.getMaxHandles() ); + + uint16_t handle = handleAlloc.alloc(); + REQUIRE(handleAlloc.isValid(handle) ); + REQUIRE(1 == handleAlloc.getNumHandles() ); + + handleAlloc.free(handle); + REQUIRE(0 == handleAlloc.getNumHandles() ); + + for (uint32_t ii = 0; ii < kMaxHandles; ++ii) + { + handle = handleAlloc.alloc(); + REQUIRE(handleAlloc.isValid(handle) ); + } + + handle = handleAlloc.alloc(); + REQUIRE(bx::kInvalidHandle == handle); + REQUIRE(!handleAlloc.isValid(handle) ); +} + +TEST_CASE("HandleAlloc2 - lowest free first", "[container]") +{ + bx::HandleAlloc2T<200> handleAlloc; + + for (uint32_t ii = 0; ii < 200; ++ii) + { + REQUIRE(ii == handleAlloc.alloc() ); + } + + handleAlloc.free(130); + handleAlloc.free(3); + handleAlloc.free(70); + + REQUIRE(197 == handleAlloc.getNumHandles() ); + REQUIRE(3 == handleAlloc.alloc() ); + REQUIRE(70 == handleAlloc.alloc() ); + REQUIRE(130 == handleAlloc.alloc() ); + REQUIRE(bx::kInvalidHandle == handleAlloc.alloc() ); +} + +TEST_CASE("HandleAlloc2 - reset", "[container]") +{ + constexpr uint16_t kMaxHandles = 64; + + bx::HandleAlloc2T handleAlloc; + + for (uint32_t ii = 0; ii < kMaxHandles; ++ii) + { + handleAlloc.alloc(); + } + + REQUIRE(kMaxHandles == handleAlloc.getNumHandles() ); + + handleAlloc.reset(); + + REQUIRE(0 == handleAlloc.getNumHandles() ); + REQUIRE(kMaxHandles == handleAlloc.getMaxHandles() ); + + uint16_t handle = handleAlloc.alloc(); + REQUIRE(0 == handle); + REQUIRE(handleAlloc.isValid(handle) ); + REQUIRE(1 == handleAlloc.getNumHandles() ); +} + +TEST_CASE("HandleAlloc2 - free and realloc", "[container]") +{ + constexpr uint16_t kMaxHandles = 4; + + bx::HandleAlloc2T handleAlloc; + + uint16_t handles[kMaxHandles]; + for (uint32_t ii = 0; ii < kMaxHandles; ++ii) + { + handles[ii] = handleAlloc.alloc(); + } + + REQUIRE(kMaxHandles == handleAlloc.getNumHandles() ); + + handleAlloc.free(handles[1]); + REQUIRE(kMaxHandles - 1 == handleAlloc.getNumHandles() ); + REQUIRE(!handleAlloc.isValid(handles[1]) ); + + uint16_t reused = handleAlloc.alloc(); + REQUIRE(handles[1] == reused); + REQUIRE(handleAlloc.isValid(reused) ); + REQUIRE(kMaxHandles == handleAlloc.getNumHandles() ); +} + +TEST_CASE("HandleAlloc2 - isValid out of range", "[container]") +{ + constexpr uint16_t kMaxHandles = 8; + + bx::HandleAlloc2T handleAlloc; + + REQUIRE(!handleAlloc.isValid(0) ); + REQUIRE(!handleAlloc.isValid(kMaxHandles) ); + REQUIRE(!handleAlloc.isValid(kMaxHandles + 100) ); + REQUIRE(!handleAlloc.isValid(UINT16_MAX) ); +} + +TEST_CASE("HandleAlloc2 - double free", "[container]") +{ + constexpr uint16_t kMaxHandles = 8; + + bx::HandleAlloc2T handleAlloc; + + uint16_t handle = handleAlloc.alloc(); + REQUIRE(1 == handleAlloc.getNumHandles() ); + + handleAlloc.free(handle); + REQUIRE(0 == handleAlloc.getNumHandles() ); + + handleAlloc.free(handle); + REQUIRE(0 == handleAlloc.getNumHandles() ); +} + +TEST_CASE("HandleAlloc2 - interleaved alloc free", "[container]") +{ + constexpr uint16_t kMaxHandles = 16; + + bx::HandleAlloc2T handleAlloc; + + uint16_t handles[kMaxHandles / 2]; + for (uint32_t ii = 0; ii < kMaxHandles / 2; ++ii) + { + handles[ii] = handleAlloc.alloc(); + REQUIRE(handleAlloc.isValid(handles[ii]) ); + } + + REQUIRE(kMaxHandles / 2 == handleAlloc.getNumHandles() ); + + for (uint32_t ii = 0; ii < kMaxHandles / 2; ii += 2) + { + handleAlloc.free(handles[ii]); + } + + REQUIRE(kMaxHandles / 2 - kMaxHandles / 4 == handleAlloc.getNumHandles() ); + + const uint32_t remaining = kMaxHandles - handleAlloc.getNumHandles(); + for (uint32_t ii = 0; ii < remaining; ++ii) + { + uint16_t handle = handleAlloc.alloc(); + REQUIRE(handleAlloc.isValid(handle) ); + } + + REQUIRE(kMaxHandles == handleAlloc.getNumHandles() ); + + uint16_t overflow = handleAlloc.alloc(); + REQUIRE(!handleAlloc.isValid(overflow) ); +} + +TEST_CASE("HandleAlloc2 - iteration", "[container]") +{ + bx::HandleAlloc2T<300> handleAlloc; + + REQUIRE(bx::kInvalidHandle == handleAlloc.findFirst() ); + + const uint16_t expected[] = { 0, 1, 63, 64, 127, 200, 299 }; + + for (uint32_t ii = 0; ii < 300; ++ii) + { + handleAlloc.alloc(); + } + + for (uint32_t ii = 0; ii < 300; ++ii) + { + bool keep = false; + for (uint32_t jj = 0; jj < BX_COUNTOF(expected); ++jj) + { + keep |= expected[jj] == ii; + } + + if (!keep) + { + handleAlloc.free(uint16_t(ii) ); + } + } + + REQUIRE(BX_COUNTOF(expected) == handleAlloc.getNumHandles() ); + + uint32_t num = 0; + for (uint16_t handle = handleAlloc.findFirst(); bx::kInvalidHandle != handle; handle = handleAlloc.findNext(handle) ) + { + REQUIRE(num < BX_COUNTOF(expected) ); + REQUIRE(expected[num] == handle); + ++num; + } + + REQUIRE(BX_COUNTOF(expected) == num); + REQUIRE(bx::kInvalidHandle == handleAlloc.findNext(299) ); +} + +TEST_CASE("HandleAlloc2 - external storage", "[container]") +{ + bx::FixedMippedBitArrayT<100> bits; + bits.setCount(100); + + bx::HandleAlloc2 handleAlloc(bits, 100); + + for (uint32_t ii = 0; ii < 100; ++ii) + { + REQUIRE(ii == handleAlloc.alloc() ); + } + + REQUIRE(bx::kInvalidHandle == handleAlloc.alloc() ); + REQUIRE(100 == handleAlloc.getNumHandles() ); + REQUIRE(99 == handleAlloc.findNext(98) ); + REQUIRE(bx::kInvalidHandle == handleAlloc.findNext(99) ); +} + +TEST_CASE("HandleAlloc2 - full word summary", "[container]") +{ + bx::HandleAlloc2T<4096> handleAlloc; + + for (uint32_t ii = 0; ii < 4096; ++ii) + { + REQUIRE(ii == handleAlloc.alloc() ); + } + + REQUIRE(bx::kInvalidHandle == handleAlloc.alloc() ); + + handleAlloc.free(4095); + handleAlloc.free(2048); + handleAlloc.free(63); + REQUIRE(4093 == handleAlloc.getNumHandles() ); + + REQUIRE(63 == handleAlloc.alloc() ); + REQUIRE(2048 == handleAlloc.alloc() ); + REQUIRE(4095 == handleAlloc.alloc() ); + REQUIRE(bx::kInvalidHandle == handleAlloc.alloc() ); + + for (uint32_t ii = 0; ii < 4096; ii += 64) + { + handleAlloc.free(uint16_t(ii + 7) ); + } + + for (uint32_t ii = 0; ii < 4096; ii += 64) + { + REQUIRE(ii + 7 == handleAlloc.alloc() ); + } + + REQUIRE(4096 == handleAlloc.getNumHandles() ); + REQUIRE(bx::kInvalidHandle == handleAlloc.alloc() ); + + handleAlloc.reset(); + REQUIRE(0 == handleAlloc.alloc() ); +} + +TEST_CASE("HandleAlloc2 - odd sizes", "[container]") +{ + bx::HandleAlloc2T<65> small; + + for (uint32_t ii = 0; ii < 65; ++ii) + { + REQUIRE(ii == small.alloc() ); + } + + REQUIRE(bx::kInvalidHandle == small.alloc() ); + small.free(64); + REQUIRE(64 == small.alloc() ); + REQUIRE(64 == small.findNext(63) ); + REQUIRE(bx::kInvalidHandle == small.findNext(64) ); + + bx::HandleAlloc2T<4097> big; + + for (uint32_t ii = 0; ii < 4097; ++ii) + { + REQUIRE(ii == big.alloc() ); + } + + REQUIRE(bx::kInvalidHandle == big.alloc() ); + big.free(4096); + big.free(0); + REQUIRE(0 == big.alloc() ); + REQUIRE(4096 == big.alloc() ); + REQUIRE(bx::kInvalidHandle == big.alloc() ); +}