// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreTypes.h" #include "Templates/UnrealTemplate.h" #include "Containers/Array.h" namespace UE::MovieScene { template struct TDynamicSparseBitSetBucketStorage; template struct TFixedSparseBitSetBucketStorage; template> struct TFixedSparseBitSet; template> struct TDynamicSparseBitSet; enum class ESparseBitSetBitResult { NewlySet, AlreadySet, }; namespace Private { static uint32 CountTrailingZeros(uint8 In) { const uint32 X = 0xFFFFFF00 | uint32(In); return FMath::CountTrailingZeros(X); } static uint32 CountTrailingZeros(uint16 In) { const uint32 X = 0xFFFF0000 | uint32(In); return FMath::CountTrailingZeros(X); } static uint32 CountTrailingZeros(uint32 In) { return FMath::CountTrailingZeros(In); } static uint32 CountTrailingZeros(uint64 In) { return static_cast(FMath::CountTrailingZeros64(In)); } /** Return a bitmask of all the bits less-than BitOffset */ template static T BitOffsetToLowBitMask(U BitOffset) { constexpr T One(1); const T Index = static_cast(BitOffset); return (One << Index)-One; } /** Return a bitmask of all the bits greater-than-or-equal to BitOffset */ template static T BitOffsetToHighBitMask(U BitOffset) { return ~BitOffsetToLowBitMask(BitOffset); } } /** * NOTE: This class is currently considered internal only, and should only be used by engine code. * A sparse bitset comprising a hash of integer indexes with set bits, and a sparse array of unsigned integers (referred to as buckets) whose width is defined by the storage. * * The maximum size bitfield that is representible by this bitset is defined as sizeof(HashType)*sizeof(BucketStorage::BucketType). For example, a 64 bit hash with 32 bit buckets * can represent a bitfield of upto 2048 bits. * * The hash allows for empty buckets to be completely omitted from memory, and affords very fast comparison for buckets that have no set bits. * This container is specialized for relatively large bitfields that have small numbers of set bits (ie, needles in haystacks) as they will provide the best memory vs CPU tradeoffs. */ template struct TFixedSparseBitSet { using BucketType = typename BucketStorage::BucketType; static constexpr uint32 HashSize = sizeof(HashType)*8; static constexpr uint32 BucketSize = sizeof(typename BucketStorage::BucketType)*8; static constexpr uint32 MaxNumBits = HashSize * BucketSize; explicit TFixedSparseBitSet() : BucketHash(0) {} template explicit TFixedSparseBitSet(StorageArgs&& ...Storage) : Buckets(Forward(Storage)...) , BucketHash(0) {} TFixedSparseBitSet(const TFixedSparseBitSet&) = default; TFixedSparseBitSet& operator=(const TFixedSparseBitSet&) = default; TFixedSparseBitSet(TFixedSparseBitSet&&) = default; TFixedSparseBitSet& operator=(TFixedSparseBitSet&&) = default; template void CopyTo(TFixedSparseBitSet& Other) const { static_assert(TFixedSparseBitSet::BucketSize == BucketSize, "Cannot copy sparse bitsets of different bucket sizes"); // Copy the buckets const uint32 NumBuckets = FMath::CountBits(Other.BucketHash); Other.Buckets.SetNum(NumBuckets); CopyToUnsafe(Other, NumBuckets); } /** Copy this bitset to another without resizing the destination's bucket storage. Bucket storage must be >= this size. */ template void CopyToUnsafe(TFixedSparseBitSet& Other, uint32 OtherBucketCapacity) const { static_assert(TFixedSparseBitSet::BucketSize == BucketSize, "Cannot copy sparse bitsets of different bucket sizes"); const uint32 ThisNumBuckets = this->NumBuckets(); checkf(OtherBucketCapacity >= ThisNumBuckets, TEXT("Attempting to copy a sparse bitset without enough capacity in the destination (%d, required %d)"), OtherBucketCapacity, ThisNumBuckets); // Copy the hash Other.BucketHash = this->BucketHash; // Copy the buckets FMemory::Memcpy(Other.Buckets.GetData(), this->Buckets.Storage.GetData(), sizeof(typename BucketStorage::BucketType)*ThisNumBuckets); } TFixedSparseBitSet& operator|=(const TFixedSparseBitSet& Other) { using namespace Private; HashType One(1u); HashType NewHash = Other.BucketHash | BucketHash; HashType OtherHash = Other.BucketHash; uint32 OtherBucketIndex = 0; uint32 OtherBucketBitIndex = Private::CountTrailingZeros(OtherHash); while (OtherBucketBitIndex < HashSize) { const HashType HashBit = HashType(1) << OtherBucketBitIndex; const uint32 ThisBucketIndex = FMath::CountBits(BucketHash & (HashBit-1)); if ((BucketHash & HashBit) == 0) { Buckets.Insert(Other.Buckets.Get(OtherBucketIndex), ThisBucketIndex); } else { Buckets.Get(ThisBucketIndex) |= Other.Buckets.Get(OtherBucketIndex); } BucketHash |= HashBit; ++OtherBucketIndex; // Mask out this bit and find the index of the next one OtherHash &= ~(One << OtherBucketBitIndex); OtherBucketBitIndex = Private::CountTrailingZeros(OtherHash); } return *this; } /** * Count the number of buckets in this bitset */ uint32 NumBuckets() const { return FMath::CountBits(this->BucketHash); } uint32 CountSetBits() const { uint32 Total = 0; const uint32 TotalNumBuckets = NumBuckets(); for (uint32 Index = 0; Index < TotalNumBuckets; ++Index) { Total += FMath::CountBits(Buckets.Get(Index)); } return Total; } uint32 GetMaxNumBits() const { return MaxNumBits; } bool IsEmpty() const { return this->BucketHash == 0; } /** * Set the bit at the specified index. * Any bits between Num and BitIndex will be considered 0. * * @return true if the bit was previously considered 0 and is now set, false if it was already set. */ ESparseBitSetBitResult SetBit(uint32 BitIndex) { CheckIndex(BitIndex); FBitOffsets Offsets(BucketHash, BitIndex); // Do we need to add a new bucket? if ( (BucketHash & Offsets.HashBit) == 0) { BucketHash |= Offsets.HashBit; Buckets.Insert(Offsets.BitMaskWithinBucket, Offsets.BucketIndex); return ESparseBitSetBitResult::NewlySet; } else if ((Buckets.Get(Offsets.BucketIndex) & Offsets.BitMaskWithinBucket) == 0) { Buckets.Get(Offsets.BucketIndex) |= Offsets.BitMaskWithinBucket; return ESparseBitSetBitResult::NewlySet; } return ESparseBitSetBitResult::AlreadySet; } /** * Check whether the specified bit index is set */ bool IsBitSet(uint32 BitIndex) const { CheckIndex(BitIndex); const uint32 Hash = BitIndex / BucketSize; const HashType HashBit = (HashType(1) << Hash); if (BucketHash & HashBit) { const uint32 BucketIndex = FMath::CountBits(BucketHash & (HashBit-1)); const uint32 ThisBitIndex = (BitIndex-BucketSize*Hash); const BucketType ThisBitMask = BucketType(1u) << ThisBitIndex; return Buckets.Get(BucketIndex) & ThisBitMask; } return false; } /** * Get the sparse bucket index of the specified bit */ int32 GetSparseBucketIndex(uint32 BitIndex) const { CheckIndex(BitIndex); const uint32 Hash = BitIndex / BucketSize; const HashType HashBit = (HashType(1) << Hash); if (BucketHash & HashBit) { uint32 BucketIndex = FMath::CountBits(BucketHash & (HashBit-1)); const uint32 ThisBitIndex = (BitIndex-BucketSize*Hash); const BucketType ThisBitMask = static_cast(BucketType(1u) << ThisBitIndex); BucketType ThisBucket = Buckets.Get(BucketIndex); if (ThisBucket & ThisBitMask) { // Compute the offset int32 SparseIndex = FMath::CountBits(ThisBucket & (ThisBitMask-1)); // Count all the preceding buckets to find the final sparse index while (BucketIndex > 0) { --BucketIndex; SparseIndex += FMath::CountBits(Buckets.Get(BucketIndex)); } return SparseIndex; } } return INDEX_NONE; } struct FIterator { FIterator() : BitSet(nullptr) , BucketBitIndex(0) , IndexWithinBucket(0) , CurrentBucket(0) { } static FIterator Begin(const TFixedSparseBitSet* InBitSet) { FIterator It; It.BitSet = InBitSet; It.CurrentBucket = 0; if (InBitSet->BucketHash != 0) { It.BucketBitIndex = Private::CountTrailingZeros(InBitSet->BucketHash); It.CurrentBucket = InBitSet->Buckets.Get(0); It.IndexWithinBucket = Private::CountTrailingZeros(It.CurrentBucket); } else { It.BucketBitIndex = HashSize; It.IndexWithinBucket = 0; } return It; } static FIterator End(const TFixedSparseBitSet* InBitSet) { FIterator It; It.BitSet = InBitSet; It.CurrentBucket = 0; It.BucketBitIndex = HashSize; It.IndexWithinBucket = 0; return It; } void operator++() { using namespace Private; // Clear the lowest 1 bit CurrentBucket = CurrentBucket & (CurrentBucket - 1); if (CurrentBucket != 0) { IndexWithinBucket = CountTrailingZeros(CurrentBucket); } else { // If this was the last bit, reset the iterator to end() if (BucketBitIndex == HashSize-1) { IndexWithinBucket = 0; BucketBitIndex = HashSize; return; } HashType UnvisitedBucketBitMask = BitOffsetToHighBitMask(BucketBitIndex+1); BucketBitIndex = CountTrailingZeros(HashType(BitSet->BucketHash & UnvisitedBucketBitMask)); // Check whether we're at the end if (BucketBitIndex == HashSize) { IndexWithinBucket = 0; } else { const uint8 NextBucketIndex = FMath::CountBits(BitSet->BucketHash & BitOffsetToLowBitMask(BucketBitIndex)); CurrentBucket = BitSet->Buckets.Get(NextBucketIndex); IndexWithinBucket = CountTrailingZeros(CurrentBucket); } } } int32 operator*() const { return BucketSize*BucketBitIndex + IndexWithinBucket; } explicit operator bool() const { return BucketBitIndex < HashSize; } friend bool operator==(const FIterator& A, const FIterator& B) { return A.BitSet == B.BitSet && A.BucketBitIndex == B.BucketBitIndex && A.IndexWithinBucket == B.IndexWithinBucket; } friend bool operator!=(const FIterator& A, const FIterator& B) { return !(A == B); } private: const TFixedSparseBitSet* BitSet; uint8 BucketBitIndex; uint8 IndexWithinBucket; BucketType CurrentBucket; }; friend FIterator begin(const TFixedSparseBitSet& In) { return FIterator::Begin(&In); } friend FIterator end(const TFixedSparseBitSet& In) { return FIterator::End(&In); } private: template friend struct TFixedSparseBitSet; FORCEINLINE void CheckIndex(uint32 BitIndex) const { checkfSlow(BitIndex < MaxNumBits, TEXT("Invalid index (%d) specified for a sparse bitset of maximum size (%d)"), BitIndex, MaxNumBits); } struct FBitOffsets { HashType HashBit; BucketType BitMaskWithinBucket; int32 BucketIndex; FBitOffsets(HashType InBucketHash, uint32 BitIndex) { const HashType Hash(BitIndex / BucketSize); HashBit = HashType(1) << Hash; BucketIndex = FMath::CountBits(InBucketHash & (HashBit-1u)); const uint32 ThisBitIndex = (BitIndex-BucketSize*Hash); BitMaskWithinBucket = BucketType(1u) << ThisBitIndex; } }; BucketStorage Buckets; HashType BucketHash; }; template struct TDynamicSparseBitSetBucketStorage { using BucketType = T; TArray> Storage; void Insert(BucketType InitialValue, int32 Index) { Storage.Insert(InitialValue, Index); } BucketType* GetData() { return Storage.GetData(); } BucketType& Get(int32 Index) { return Storage[Index]; } BucketType Get(int32 Index) const { return Storage[Index]; } }; template struct TDynamicSparseBitSetBucketStorage { using BucketType = T; TArray Storage; void Insert(BucketType InitialValue, int32 Index) { Storage.Insert(InitialValue, Index); } BucketType* GetData() { return Storage.GetData(); } BucketType& Get(int32 Index) { return Storage[Index]; } BucketType Get(int32 Index) const { return Storage[Index]; } }; template struct TFixedSparseBitSetBucketStorage { using BucketType = T; explicit TFixedSparseBitSetBucketStorage() : Storage(nullptr) {} explicit TFixedSparseBitSetBucketStorage(BucketType* StoragePtr) : Storage(StoragePtr) {} TFixedSparseBitSetBucketStorage(const TFixedSparseBitSetBucketStorage&) = delete; void operator=(const TFixedSparseBitSetBucketStorage&) = delete; TFixedSparseBitSetBucketStorage(TFixedSparseBitSetBucketStorage&&) = delete; void operator=(TFixedSparseBitSetBucketStorage&&) = delete; BucketType* Storage; BucketType* GetData() { return Storage; } BucketType& Get(int32 Index) { return Storage[Index]; } BucketType Get(int32 Index) const { return Storage[Index]; } }; /** * NOTE: This class is currently considered internal only, and should only be used by engine code. * A dynamically sized sparse bitset comprising multiple TFixedSparseBitSets. * * In theory this class supports the full integer range, it is optimized for small numbers of set bits within a large range, ideally when they occupy the same adjacent space. */ template struct TDynamicSparseBitSet { /** * Get the maximum number of bits that this bitset supports */ uint32 GetMaxNumBits() const { return MAX_uint32; } /** * Set the bit at the specified index. * Any bits between Num and BitIndex will be considered 0. * * @return true if the bit was previously considered 0 and is now set, false if it was already set. */ ESparseBitSetBitResult SetBit(uint32 Bit) { const uint32 Bucket = Bit / NumBitsInBucket; Bit -= Bucket*NumBitsInBucket; FEntry* EntrPtr = Entries.GetData(); const int32 Num = Entries.Num(); for (int32 EntryIndex = 0; EntryIndex < Num; ++EntryIndex) { if (EntrPtr[EntryIndex].Offset == Bucket) { return EntrPtr[EntryIndex].Bits.SetBit(Bit); } else if (EntrPtr[EntryIndex].Offset > Bucket) { Entries.InsertUninitialized(EntryIndex); new(&Entries[EntryIndex]) FEntry(Bucket, Bit); return ESparseBitSetBitResult::NewlySet; } } Entries.Emplace(Bucket, Bit); return ESparseBitSetBitResult::NewlySet; } /** * Check whether this container has any bits set */ bool IsEmpty() const { return Entries.Num() == 0; } /** * Check whether the specified bit index is set */ bool IsBitSet(uint32 Bit) const { const uint32 Bucket = Bit / NumBitsInBucket; const FEntry* EntrPtr = Entries.GetData(); const int32 Num = Entries.Num(); for (int32 EntryIndex = 0; EntryIndex < Num; ++EntryIndex) { if (EntrPtr[EntryIndex].Offset == Bucket) { return EntrPtr[EntryIndex].Bits.IsBitSet(Bit); } if (EntrPtr[EntryIndex].Offset > Bucket) { return false; } } return false; } /** * Count the total number of set bits in this container */ uint32 CountSetBits() const { uint32 SetBits = 0; for (const FEntry& Entry : Entries) { SetBits += Entry.Bits.CountSetBits(); } return SetBits; } TDynamicSparseBitSet& operator|=(const TDynamicSparseBitSet& Other) { if (Other.Entries.Num() == 0) { return *this; } if (Entries.Num() == 0) { *this = Other; return *this; } int32 ThisIndex = 0; int32 OtherIndex = 0; while (OtherIndex < Other.Entries.Num() && Other.Entries[OtherIndex].Offset < Entries[0].Offset) { ++OtherIndex; } if (OtherIndex > 0) { Entries.Insert(Other.Entries.GetData(), OtherIndex, 0); } while (OtherIndex < Other.Entries.Num() && ThisIndex < Entries.Num()) { if (Other.Entries[OtherIndex].Offset < Entries[ThisIndex].Offset) { Entries.Insert(Other.Entries[OtherIndex], ThisIndex); ++OtherIndex; } else if (Other.Entries[OtherIndex].Offset == Entries[ThisIndex].Offset) { Entries[ThisIndex].Bits |= Other.Entries[OtherIndex].Bits; ++OtherIndex; ++ThisIndex; } else { ++ThisIndex; } } return *this; } private: using FBucketBitSet = TFixedSparseBitSet; static constexpr uint32 NumBitsInBucket = FBucketBitSet::MaxNumBits; struct FEntry { FEntry(uint32 InOffset) : Offset(InOffset) { } FEntry(uint32 InOffset, uint32 InBit) : Offset(InOffset) { checkSlow(InBit < FBucketBitSet::MaxNumBits); Bits.SetBit(InBit); } FBucketBitSet Bits; uint32 Offset; }; public: struct FIterator { static FIterator Begin(const TDynamicSparseBitSet* InBitSet) { FIterator It; It.Entries = InBitSet->Entries.GetData(); It.NumEntries = InBitSet->Entries.Num(); It.EntryIndex = 0; It.CurrentOffsetInBits = 0; if (It.NumEntries != 0) { It.CurrentOffsetInBits = It.Entries[0].Offset * NumBitsInBucket; It.BucketIt = BucketIterator::Begin(&It.Entries[0].Bits); } return It; } static FIterator End(const TDynamicSparseBitSet* InBitSet) { FIterator It; It.Entries = InBitSet->Entries.GetData(); It.NumEntries = InBitSet->Entries.Num(); It.EntryIndex = It.NumEntries; It.CurrentOffsetInBits = 0; return It; } void operator++() { using namespace Private; ++BucketIt; if (!BucketIt) { ++EntryIndex; if (EntryIndex < NumEntries) { CurrentOffsetInBits = Entries[EntryIndex].Offset * NumBitsInBucket; BucketIt = BucketIterator::Begin(&Entries[EntryIndex].Bits); } else { CurrentOffsetInBits = 0; BucketIt = BucketIterator(); } } } int32 operator*() const { return CurrentOffsetInBits + *BucketIt; } explicit operator bool() const { return EntryIndex < NumEntries; } friend bool operator==(const FIterator& A, const FIterator& B) { return A.Entries == B.Entries && A.EntryIndex == B.EntryIndex && A.BucketIt == B.BucketIt; } friend bool operator!=(const FIterator& A, const FIterator& B) { return !(A == B); } private: FIterator() = default; using BucketIterator = typename TFixedSparseBitSet::FIterator; const typename TDynamicSparseBitSet::FEntry* Entries; BucketIterator BucketIt; int32 NumEntries; int32 EntryIndex; int32 CurrentOffsetInBits; }; friend FIterator begin(const TDynamicSparseBitSet& In) { return FIterator::Begin(&In); } friend FIterator end(const TDynamicSparseBitSet& In) { return FIterator::End(&In); } TArray Entries; }; } // namespace UE::MovieScene