// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "HAL/PlatformMath.h" #include "HAL/UnrealMemory.h" //////////////////////////////////////////////////////////////////////////////////////////////////// // FPow2ChunkedArray // // A chunk based array where each chunk is twice as large as the previous one. // Adding and accessing elements are O(1) // Add is threadsafe and keeping references is safe since it never reallocates template class FPow2ChunkedArray { public: enum : uint32 { SkipCount = FGenericPlatformMath::CeilLogTwo(MinSize) }; enum : uint32 { BucketCount = FGenericPlatformMath::CeilLogTwo(MaxSize) - SkipCount + 1 }; inline FPow2ChunkedArray(); inline ~FPow2ChunkedArray(); inline T& Add(const T& Value, uint32& OutIndex); inline T& Add(const T& Value); inline T& Add(T&& Value); inline const T& operator[](uint32 Index) const; inline T& operator[](uint32 Index); inline uint32 Num() const; inline const T& GetElementAt(uint32 Index) const; inline void* AddUninitialized(uint32& OutIndex); inline uint32 GetBucketIndex(uint32 Index) const; inline uint32 GetBucketStart(uint32 BucketIndex) const; inline uint32 GetBucketSize(uint32 BucketIndex) const; private: std::atomic Size; volatile int64 Buckets[BucketCount]; }; //////////////////////////////////////////////////////////////////////////////////////////////////// // Implementation template FPow2ChunkedArray::FPow2ChunkedArray() { FMemory::Memset((int64*)Buckets, 0, sizeof(Buckets)); } template FPow2ChunkedArray::~FPow2ChunkedArray() { uint32 Left = Size; for (uint32 BucketIndex=0; Left; ++BucketIndex) { T* Elements = reinterpret_cast(Buckets[BucketIndex]); uint32 LeftInBucket = FPlatformMath::Min(Left, GetBucketSize(BucketIndex)); if (!std::is_trivially_destructible_v) { for (uint32 I=0, E=LeftInBucket; I!=E; ++I) { Elements[I].~T(); } } Left -= LeftInBucket; FMemory::Free(Elements); } } template T& FPow2ChunkedArray::Add(const T& Value, uint32& OutIndex) { return *new (AddUninitialized(OutIndex)) T(Value); } template T& FPow2ChunkedArray::Add(const T& Value) { uint32 OutIndex; return *new (AddUninitialized(OutIndex)) T(Value); } template T& FPow2ChunkedArray::Add(T&& Value) { uint32 OutIndex; return *new (AddUninitialized(OutIndex)) T(std::move(Value)); } template const T& FPow2ChunkedArray::operator[](uint32 Index) const { return GetElementAt(Index); } template T& FPow2ChunkedArray::operator[](uint32 Index) { return const_cast(GetElementAt(Index)); } template uint32 FPow2ChunkedArray::Num() const { return Size; } template const T& FPow2ChunkedArray::GetElementAt(uint32 Index) const { check(Index < Size); uint32 BucketIndex = GetBucketIndex(Index); uint32 BucketStart = GetBucketStart(BucketIndex); uint32 BucketOffset = Index - BucketStart; return reinterpret_cast(Buckets[BucketIndex])[BucketOffset]; } template void* FPow2ChunkedArray::AddUninitialized(uint32& OutIndex) { uint32 Index = Size++; uint32 BucketIndex = GetBucketIndex(Index); volatile int64& BucketRef = Buckets[BucketIndex]; int64 BucketRes = FPlatformAtomics::AtomicRead(&BucketRef); if (!BucketRes) { void* NewPtr = FMemory::Malloc(GetBucketSize(BucketIndex) * sizeof(T), alignof(T)); BucketRes = FPlatformAtomics::InterlockedCompareExchange(&BucketRef, int64(NewPtr), 0); if (BucketRes) { FMemory::Free(NewPtr); } else { BucketRes = int64(NewPtr); } } uint32 BucketStart = GetBucketStart(BucketIndex); uint32 BucketOffset = Index - BucketStart; OutIndex = Index; return reinterpret_cast(BucketRes) + BucketOffset; } template uint32 FPow2ChunkedArray::GetBucketIndex(uint32 Index) const { return uint32(FPlatformMath::FloorLog2(Index / 16 + 1)); } template uint32 FPow2ChunkedArray::GetBucketStart(uint32 BucketIndex) const { return BucketIndex ? 16 * ((1 << BucketIndex) - 1) : 0; } template uint32 FPow2ChunkedArray::GetBucketSize(uint32 BucketIndex) const { return FPlatformMath::Max(MinSize, 1u << (BucketIndex + SkipCount)); } ////////////////////////////////////////////////////////////////////////////////////////////////////