Files
UnrealEngine/Engine/Source/Runtime/MovieScene/Public/Containers/SparseBitSet.h
2025-05-18 13:04:45 +08:00

740 lines
19 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreTypes.h"
#include "Templates/UnrealTemplate.h"
#include "Containers/Array.h"
namespace UE::MovieScene
{
template<typename T, int32 InlineSize>
struct TDynamicSparseBitSetBucketStorage;
template<typename T>
struct TFixedSparseBitSetBucketStorage;
template<typename HashType, typename BucketStorage = TDynamicSparseBitSetBucketStorage<uint8, 4>>
struct TFixedSparseBitSet;
template<typename HashType, typename BucketStorage = TDynamicSparseBitSetBucketStorage<uint8, 4>>
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<uint32>(FMath::CountTrailingZeros64(In));
}
/** Return a bitmask of all the bits less-than BitOffset */
template<typename T, typename U>
static T BitOffsetToLowBitMask(U BitOffset)
{
constexpr T One(1);
const T Index = static_cast<T>(BitOffset);
return (One << Index)-One;
}
/** Return a bitmask of all the bits greater-than-or-equal to BitOffset */
template<typename T, typename U>
static T BitOffsetToHighBitMask(U BitOffset)
{
return ~BitOffsetToLowBitMask<T>(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<typename HashType, typename BucketStorage>
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<typename ...StorageArgs>
explicit TFixedSparseBitSet(StorageArgs&& ...Storage)
: Buckets(Forward<StorageArgs>(Storage)...)
, BucketHash(0)
{}
TFixedSparseBitSet(const TFixedSparseBitSet&) = default;
TFixedSparseBitSet& operator=(const TFixedSparseBitSet&) = default;
TFixedSparseBitSet(TFixedSparseBitSet&&) = default;
TFixedSparseBitSet& operator=(TFixedSparseBitSet&&) = default;
template<typename OtherHashType, typename OtherStorageType>
void CopyTo(TFixedSparseBitSet<OtherHashType, OtherStorageType>& Other) const
{
static_assert(TFixedSparseBitSet<OtherHashType, OtherStorageType>::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<typename OtherHashType, typename OtherStorageType>
void CopyToUnsafe(TFixedSparseBitSet<OtherHashType, OtherStorageType>& Other, uint32 OtherBucketCapacity) const
{
static_assert(TFixedSparseBitSet<OtherHashType, OtherStorageType>::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>(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<HashType, BucketStorage>* 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<HashType, BucketStorage>* 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<HashType>(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<HashType>(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<HashType, BucketStorage>* BitSet;
uint8 BucketBitIndex;
uint8 IndexWithinBucket;
BucketType CurrentBucket;
};
friend FIterator begin(const TFixedSparseBitSet<HashType, BucketStorage>& In) { return FIterator::Begin(&In); }
friend FIterator end(const TFixedSparseBitSet<HashType, BucketStorage>& In) { return FIterator::End(&In); }
private:
template<typename, typename>
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<typename T, int32 InlineSize = 8>
struct TDynamicSparseBitSetBucketStorage
{
using BucketType = T;
TArray<BucketType, TInlineAllocator<InlineSize>> 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<typename T>
struct TDynamicSparseBitSetBucketStorage<T, 0>
{
using BucketType = T;
TArray<BucketType> 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<typename T>
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<typename HashType, typename BucketStorage>
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<HashType, BucketStorage>& operator|=(const TDynamicSparseBitSet<HashType, BucketStorage>& 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<HashType, BucketStorage>;
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<HashType, BucketStorage>* 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<HashType, BucketStorage>* 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<HashType, BucketStorage>::FIterator;
const typename TDynamicSparseBitSet<HashType, BucketStorage>::FEntry* Entries;
BucketIterator BucketIt;
int32 NumEntries;
int32 EntryIndex;
int32 CurrentOffsetInBits;
};
friend FIterator begin(const TDynamicSparseBitSet<HashType, BucketStorage>& In) { return FIterator::Begin(&In); }
friend FIterator end(const TDynamicSparseBitSet<HashType, BucketStorage>& In) { return FIterator::End(&In); }
TArray<FEntry> Entries;
};
} // namespace UE::MovieScene