Files
UnrealEngine/Engine/Source/Developer/TraceServices/Public/Common/PagedArray.h
2025-05-18 13:04:45 +08:00

1138 lines
33 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "TraceServices/Containers/Allocators.h"
#include "Containers/Array.h"
#include "Containers/ArrayView.h"
#include "Math/Interval.h"
#include "Templates/Function.h"
#include "Templates/IsArithmetic.h"
#include "Templates/Less.h"
#include "Templates/Requires.h"
#ifndef TRACESERVICES_PAGED_ARRAY_ITERATOR_V2
#define TRACESERVICES_PAGED_ARRAY_ITERATOR_V2 0 // enables a simpler implementation of TPagedArrayIterator, for debug purposes
#endif
#ifndef TRACESERVICES_PAGED_ARRAY_ITERATOR_DEBUG_ENABLED
#define TRACESERVICES_PAGED_ARRAY_ITERATOR_DEBUG_ENABLED 0
#endif
namespace TraceServices {
template<typename ItemType>
struct TPagedArrayPage
{
ItemType* Items = nullptr;
uint64 Count = 0;
};
template<typename ItemType>
inline const ItemType* GetData(const TPagedArrayPage<ItemType>& Page)
{
return Page.Items;
}
template<typename ItemType>
inline SIZE_T GetNum(const TPagedArrayPage<ItemType>& Page)
{
return Page.Count;
}
template<typename ItemType>
inline const ItemType* GetFirstItem(const TPagedArrayPage<ItemType>& Page)
{
return Page.Items;
}
template<typename ItemType>
inline const ItemType* GetLastItem(const TPagedArrayPage<ItemType>& Page)
{
if (Page.Count)
{
return Page.Items + Page.Count - 1;
}
else
{
return nullptr;
}
}
template<typename ItemType, typename PageType>
class TPagedArray;
#if !TRACESERVICES_PAGED_ARRAY_ITERATOR_V2
template<typename ItemType, typename PageType>
class TPagedArrayIterator
{
public:
TPagedArrayIterator()
{
}
TPagedArrayIterator(const TPagedArray<ItemType, PageType>& InOuter, uint64 InItemIndex)
: Outer(&InOuter)
{
#if TRACESERVICES_PAGED_ARRAY_ITERATOR_DEBUG_ENABLED
TotalItemCount = Outer->Num();
TotalPageCount = Outer->PagesArray.Num();
#endif
SetPositionInternal(InItemIndex);
DebugCheckState();
}
//////////////////////////////////////////////////
// Page Iterator
uint64 GetCurrentPageIndex() const
{
return CurrentPageIndex;
}
const PageType* GetCurrentPage() const
{
return Outer->FirstPage + CurrentPageIndex;
}
const PageType* SetCurrentPage(uint64 PageIndex)
{
DebugCheckState();
uint64 ItemIndex = PageIndex * Outer->PageSize;
if (ItemIndex >= Outer->Num()) // end()
{
ItemIndex = Outer->Num();
CurrentPageIndex = ItemIndex / Outer->PageSize;
CurrentPageFirstItem = nullptr;
CurrentPageLastItem = nullptr;
CurrentItemIndex = ItemIndex;
CurrentItem = nullptr;
DebugCheckState();
return nullptr;
}
else
{
check(PageIndex < Outer->PagesArray.Num());
CurrentPageIndex = PageIndex;
OnCurrentPageChanged();
CurrentItemIndex = ItemIndex;
PageType* CurrentPage = Outer->FirstPage + CurrentPageIndex;
check(CurrentPage->Count > 0);
CurrentItem = CurrentPage->Items;
DebugCheckState();
return CurrentPage;
}
}
const PageType* PrevPage()
{
DebugCheckState();
if (CurrentPageIndex == 0)
{
CurrentItem = nullptr;
CurrentPageFirstItem = nullptr;
CurrentPageLastItem = nullptr;
CurrentItemIndex = Outer->Num();
DebugCheckState();
return nullptr;
}
--CurrentPageIndex;
OnCurrentPageChanged();
CurrentItemIndex = CurrentPageIndex * Outer->PageSize + (CurrentPageLastItem - CurrentPageFirstItem);
CurrentItem = CurrentPageLastItem;
DebugCheckState();
return GetCurrentPage();
}
const PageType* NextPage()
{
DebugCheckState();
if (CurrentPageIndex == Outer->PagesArray.Num() - 1)
{
CurrentItem = nullptr;
CurrentPageFirstItem = nullptr;
CurrentPageLastItem = nullptr;
CurrentItemIndex = Outer->Num();
DebugCheckState();
return nullptr;
}
++CurrentPageIndex;
OnCurrentPageChanged();
CurrentItemIndex = CurrentPageIndex * Outer->PageSize;
CurrentItem = CurrentPageFirstItem;
DebugCheckState();
return GetCurrentPage();
}
//////////////////////////////////////////////////
// Item Iterator
uint64 GetCurrentItemIndex() const
{
return CurrentItemIndex;
}
const ItemType* GetCurrentItem() const
{
return CurrentItem;
}
const ItemType* SetPosition(uint64 InItemIndex)
{
DebugCheckState();
SetPositionInternal(InItemIndex);
DebugCheckState();
return CurrentItem;
}
const ItemType* PrevItem()
{
if (CurrentItem == CurrentPageFirstItem)
{
if (!PrevPage())
{
return nullptr;
}
else
{
return CurrentItem;
}
}
DebugCheckState();
--CurrentItemIndex;
--CurrentItem;
DebugCheckState();
return CurrentItem;
}
const ItemType* NextItem()
{
if (CurrentItem == CurrentPageLastItem)
{
if (!NextPage())
{
return nullptr;
}
else
{
return CurrentItem;
}
}
DebugCheckState();
++CurrentItemIndex;
++CurrentItem;
DebugCheckState();
return CurrentItem;
}
//////////////////////////////////////////////////
// operators
const ItemType& operator*() const
{
return *CurrentItem;
}
const ItemType* operator->() const
{
return CurrentItem;
}
explicit operator bool() const
{
return CurrentItem != nullptr;
}
TPagedArrayIterator& operator++()
{
NextItem();
return *this;
}
TPagedArrayIterator operator++(int)
{
TPagedArrayIterator Tmp(*this);
NextItem();
return Tmp;
}
TPagedArrayIterator& operator--()
{
PrevItem();
return *this;
}
TPagedArrayIterator operator--(int)
{
TPagedArrayIterator Tmp(*this);
PrevItem();
return Tmp;
}
private:
void OnCurrentPageChanged()
{
PageType* CurrentPage = Outer->FirstPage + CurrentPageIndex;
CurrentPageFirstItem = CurrentPage->Items;
if (CurrentPage->Items)
{
CurrentPageLastItem = CurrentPage->Items + CurrentPage->Count - 1;
}
else
{
CurrentPageLastItem = nullptr;
}
}
void SetPositionInternal(uint64 InItemIndex)
{
CurrentPageIndex = InItemIndex / Outer->PageSize;
CurrentItemIndex = InItemIndex;
if (InItemIndex == Outer->Num()) // end()
{
CurrentPageFirstItem = nullptr;
CurrentPageLastItem = nullptr;
CurrentItem = nullptr;
}
else
{
check(InItemIndex < Outer->Num());
check(CurrentPageIndex < Outer->PagesArray.Num());
OnCurrentPageChanged();
PageType* CurrentPage = Outer->FirstPage + CurrentPageIndex;
uint64 ItemIndexInPage = InItemIndex % Outer->PageSize;
check(ItemIndexInPage < CurrentPage->Count);
CurrentItem = CurrentPage->Items + ItemIndexInPage;
}
}
FORCEINLINE friend bool operator!=(const TPagedArrayIterator& Lhs, const TPagedArrayIterator& Rhs)
{
checkSlow(Lhs.Outer == Rhs.Outer); // Needs to be iterators of the same array
return Lhs.CurrentItemIndex != Rhs.CurrentItemIndex;
}
#if TRACESERVICES_PAGED_ARRAY_ITERATOR_DEBUG_ENABLED
void DebugCheckState()
{
if (Outer)
{
check(TotalPageCount == Outer->PagesArray.Num());
check(TotalItemCount == Outer->Num());
if (CurrentItemIndex == TotalItemCount) // end()
{
check(CurrentItem == nullptr);
check(CurrentPageIndex <= TotalPageCount);
check(CurrentPageFirstItem == nullptr);
check(CurrentPageLastItem == nullptr);
}
else
{
check(CurrentItemIndex < TotalItemCount);
check(CurrentItem != nullptr);
check(CurrentPageIndex < TotalPageCount);
PageType* CurrentPage = Outer->FirstPage + CurrentPageIndex;
check(CurrentPage->Count > 0);
uint64 ItemIndexInPage = CurrentItemIndex % Outer->PageSize;
check(ItemIndexInPage < CurrentPage->Count);
check(CurrentPageFirstItem == CurrentPage->Items);
check(CurrentPageLastItem == CurrentPage->Items + CurrentPage->Count - 1);
check(CurrentItem == CurrentPage->Items + ItemIndexInPage);
}
}
else
{
check(TotalPageCount == 0);
check(TotalItemCount == 0);
check(CurrentPageIndex == 0);
check(CurrentPageFirstItem == nullptr);
check(CurrentPageLastItem == nullptr);
check(CurrentItemIndex == 0);
check(CurrentItem == nullptr);
}
}
#else
void DebugCheckState()
{
}
#endif
private:
const TPagedArray<ItemType, PageType>* Outer = nullptr;
uint64 CurrentPageIndex = 0;
const ItemType* CurrentPageFirstItem = nullptr;
const ItemType* CurrentPageLastItem = nullptr;
uint64 CurrentItemIndex = 0;
const ItemType* CurrentItem = nullptr;
#if TRACESERVICES_PAGED_ARRAY_ITERATOR_DEBUG_ENABLED
uint64 TotalPageCount = 0;
uint64 TotalItemCount = 0;
#endif
};
#else // TRACESERVICES_PAGED_ARRAY_ITERATOR_V2
template<typename ItemType, typename PageType>
class TPagedArrayIterator
{
public:
TPagedArrayIterator()
{
}
TPagedArrayIterator(const TPagedArray<ItemType, PageType>& InOuter, uint64 InItemIndex)
: Outer(&InOuter)
, CurrentItemIndex(InItemIndex)
{
}
//////////////////////////////////////////////////
// Page Iterator
uint64 GetCurrentPageIndex() const
{
return CurrentItemIndex / Outer->PageSize;
}
const PageType* GetCurrentPage() const
{
return Outer->FirstPage + CurrentItemIndex / Outer->PageSize;
}
const PageType* SetCurrentPage(uint64 PageIndex)
{
CurrentItemIndex = PageIndex * Outer->PageSize;
return Outer->FirstPage + PageIndex;
}
const PageType* PrevPage()
{
uint64 PageIndex = CurrentItemIndex / Outer->PageSize;
if (PageIndex > 0)
{
--PageIndex;
CurrentItemIndex = PageIndex * Outer->PageSize;
return Outer->FirstPage + PageIndex;
}
else
{
CurrentItemIndex = Outer->Num();
return nullptr;
}
}
const PageType* NextPage()
{
uint64 PageIndex = CurrentItemIndex / Outer->PageSize + 1;
if (PageIndex < Outer->NumPages())
{
CurrentItemIndex = PageIndex * Outer->PageSize;
return Outer->FirstPage + PageIndex;
}
else
{
CurrentItemIndex = Outer->Num();
return nullptr;
}
}
//////////////////////////////////////////////////
// Item Iterator
uint64 GetCurrentItemIndex() const
{
return CurrentItemIndex;
}
const ItemType* GetCurrentItem() const
{
return &(*Outer)[CurrentItemIndex];
}
const ItemType* SetPosition(uint64 Index)
{
CurrentItemIndex = Index;
return GetCurrentItem();
}
const ItemType* PrevItem()
{
if (CurrentItemIndex > 0)
{
--CurrentItemIndex;
return GetCurrentItem();
}
else
{
return nullptr;
}
}
const ItemType* NextItem()
{
if (CurrentItemIndex + 1 < Outer->Num())
{
++CurrentItemIndex;
return GetCurrentItem();
}
else
{
CurrentItemIndex = Outer->Num();
return nullptr;
}
}
//////////////////////////////////////////////////
// operators
const ItemType& operator*() const
{
return (*Outer)[CurrentItemIndex];
}
const ItemType* operator->() const
{
return &(*Outer)[CurrentItemIndex];
}
explicit operator bool() const
{
return CurrentItemIndex < Outer->Num();
}
TPagedArrayIterator& operator++()
{
++CurrentItemIndex;
return *this;
}
TPagedArrayIterator operator++(int)
{
TPagedArrayIterator Tmp(*this);
++CurrentItemIndex;
return Tmp;
}
TPagedArrayIterator& operator--()
{
--CurrentItemIndex;
return *this;
}
TPagedArrayIterator operator--(int)
{
TPagedArrayIterator Tmp(*this);
--CurrentItemIndex;
return Tmp;
}
private:
FORCEINLINE friend bool operator!=(const TPagedArrayIterator& Lhs, const TPagedArrayIterator& Rhs)
{
checkSlow(Lhs.Outer == Rhs.Outer); // Needs to be iterators of the same array
return Lhs.CurrentItemIndex != Rhs.CurrentItemIndex;
}
const TPagedArray<ItemType, PageType>* Outer = nullptr;
uint64 CurrentItemIndex = 0;
};
#endif // TRACESERVICES_PAGED_ARRAY_ITERATOR_V2
template<typename InItemType, typename InPageType = TPagedArrayPage<InItemType>>
class TPagedArray
{
public:
typedef InItemType ItemType;
typedef InPageType PageType;
typedef TPagedArrayIterator<InItemType, InPageType> TIterator;
TPagedArray(ILinearAllocator& InAllocator, uint64 InPageSize)
: Allocator(InAllocator)
, PageSize(InPageSize)
{
}
~TPagedArray()
{
for (PageType& Page : PagesArray)
{
ItemType* PageEnd = Page.Items + Page.Count;
for (ItemType* Item = Page.Items; Item != PageEnd; ++Item)
{
Item->~ItemType();
}
}
}
uint64 Num() const
{
return TotalItemCount;
}
uint64 GetPageSize() const
{
return PageSize;
}
uint64 NumPages() const
{
return PagesArray.Num();
}
ItemType& PushBack()
{
if (!LastPage || LastPage->Count == PageSize)
{
LastPage = &PagesArray.AddDefaulted_GetRef();
FirstPage = PagesArray.GetData();
LastPage->Items = reinterpret_cast<ItemType*>(Allocator.Allocate(PageSize * sizeof(ItemType)));
}
++TotalItemCount;
ItemType* ItemPtr = LastPage->Items + LastPage->Count;
new (ItemPtr) ItemType();
++LastPage->Count;
return *ItemPtr;
}
template <typename... ArgsType>
ItemType& EmplaceBack(ArgsType&&... Args)
{
if (!LastPage || LastPage->Count == PageSize)
{
LastPage = &PagesArray.AddDefaulted_GetRef();
FirstPage = PagesArray.GetData();
LastPage->Items = reinterpret_cast<ItemType*>(Allocator.Allocate(PageSize * sizeof(ItemType)));
}
++TotalItemCount;
ItemType* ItemPtr = LastPage->Items + LastPage->Count;
new (ItemPtr) ItemType(Forward<ArgsType>(Args)...);
++LastPage->Count;
return *ItemPtr;
}
ItemType& Insert(uint64 Index)
{
if (Index >= TotalItemCount)
{
return PushBack();
}
PushBack();
uint64 PageIndex = Index / PageSize;
uint64 PageItemIndex = Index % PageSize;
for (uint64 CurrentPageIndex = PagesArray.Num() - 1; CurrentPageIndex > PageIndex; --CurrentPageIndex)
{
PageType* CurrentPage = FirstPage + CurrentPageIndex;
memmove(CurrentPage->Items + 1, CurrentPage->Items, sizeof(ItemType) * (CurrentPage->Count - 1));
PageType* PrevPage = CurrentPage - 1;
memcpy(CurrentPage->Items, PrevPage->Items + PrevPage->Count - 1, sizeof(ItemType));
}
PageType* Page = FirstPage + PageIndex;
memmove(Page->Items + PageItemIndex + 1, Page->Items + PageItemIndex, sizeof(ItemType) * (Page->Count - PageItemIndex - 1));
return Page->Items[PageItemIndex];
}
PageType* GetLastPage()
{
return LastPage;
}
const PageType* GetLastPage() const
{
return LastPage;
}
PageType* GetPage(uint64 PageIndex)
{
return FirstPage + PageIndex;
}
const PageType* GetPage(uint64 PageIndex) const
{
return FirstPage + PageIndex;
}
PageType* GetItemPage(uint64 ItemIndex)
{
uint64 PageIndex = ItemIndex / PageSize;
return FirstPage + PageIndex;
}
const PageType* GetItemPage(uint64 ItemIndex) const
{
uint64 PageIndex = ItemIndex / PageSize;
return FirstPage + PageIndex;
}
TIterator GetIterator() const
{
return TIterator(*this, 0);
}
TIterator GetIteratorFromPage(uint64 PageIndex) const
{
return TIterator(*this, PageIndex * PageSize);
}
TIterator GetIteratorFromItem(uint64 ItemIndex) const
{
return TIterator(*this, ItemIndex);
}
const PageType* GetPages() const
{
return FirstPage;
}
ItemType& operator[](uint64 Index)
{
uint64 PageIndex = Index / PageSize;
uint64 IndexInPage = Index % PageSize;
PageType* Page = FirstPage + PageIndex;
ItemType* Item = Page->Items + IndexInPage;
return *Item;
}
const ItemType& operator[](uint64 Index) const
{
return const_cast<TPagedArray&>(*this)[Index];
}
ItemType& First()
{
ItemType* Item = FirstPage->Items;
return *Item;
}
const ItemType& First() const
{
const ItemType* Item = FirstPage->Items;
return *Item;
}
ItemType& Last()
{
ItemType* Item = LastPage->Items + LastPage->Count - 1;
return *Item;
}
const ItemType& Last() const
{
const ItemType* Item = LastPage->Items + LastPage->Count - 1;
return *Item;
}
FORCEINLINE TIterator begin() { return TIterator(*this, 0); }
FORCEINLINE TIterator begin() const { return TIterator(*this, 0); }
FORCEINLINE TIterator end() { return TIterator(*this, TotalItemCount); }
FORCEINLINE TIterator end() const { return TIterator(*this, TotalItemCount); }
private:
template<typename ItemType, typename PageType>
friend class TPagedArrayIterator;
ILinearAllocator& Allocator;
TArray<PageType> PagesArray;
PageType* FirstPage = nullptr;
PageType* LastPage = nullptr;
uint64 PageSize;
uint64 TotalItemCount = 0;
};
template<typename ItemType, typename PageType>
inline const PageType* GetData(const TPagedArray<ItemType, PageType>& PagedArray)
{
return PagedArray.GetPages();
}
template<typename ItemType, typename PageType>
inline SIZE_T GetNum(const TPagedArray<ItemType, PageType>& PagedArray)
{
return PagedArray.NumPages();
}
/**
* Use binary search to find the first and last element inside a TPagedArray that overlaps a given input interval.
* This requires the elements in the array to be sorted by the value returned from the Projection.
* Example usage for Timeline events would require a projection that returns Item.StartTime and the resulting range.Min
* will point to the last element where Item.End > StartTime and range.Max to the last element where
* Item.StartTime < EndTime.
*/
template<typename ItemType, typename PageType>
FInt32Interval GetElementRangeOverlappingGivenRange(const TPagedArray<ItemType, PageType>& PagedArray,
double StartTime, double EndTime,
TFunctionRef<double(const ItemType&)> ItemStartProjection,
TFunctionRef<double(const ItemType&)> ItemEndProjection
)
{
FInt32Interval Result = { -1, -1 };
check(EndTime >= StartTime);
const PageType* PageData = PagedArray.GetPages();
if (!PageData)
{
return Result;
}
const int32 NumPoints = static_cast<int32>(PagedArray.Num());
const int32 NumPages = static_cast<int32>(PagedArray.NumPages());
const int32 PageSize = static_cast<int32>(PagedArray.GetPageSize());
if (ItemStartProjection(PagedArray.First()) > EndTime || ItemEndProjection(PagedArray.Last()) <= StartTime)
{
return Result;
}
TArrayView<const PageType, int32> Pages = MakeArrayView(PageData, NumPages);
// find the page before the first page that's already inside the range (thus -1)
int32 StartPageIndex = Algo::UpperBoundBy(Pages, StartTime,
[&ItemEndProjection](const PageType& Page) { return ItemEndProjection(Page.Items[0]); });
StartPageIndex -= 1;
if (StartPageIndex < 0)
{
Result.Min = 0;
StartPageIndex = 0;
}
else
{
// if we went past the end with StartPageIndex we still have to search the last page
const PageType& Page = PageData[StartPageIndex];
TArrayView<ItemType, int32> PageValues(Page.Items, static_cast<int32>(Page.Count));
const int32 Index = Algo::UpperBoundBy(PageValues, StartTime, ItemEndProjection);
check(Index >= 0);
// Index may point past the end of the page, but the first item in the next page could be valid
// as long as we're not in the last page
if (Index >= Page.Count && StartPageIndex + 1 == NumPages )
{
return Result;
}
Result.Min = StartPageIndex * PageSize + Index;
check(Index <= NumPoints);
}
TArrayView<const PageType, int32> RemainingPages = MakeArrayView(&PageData[StartPageIndex], NumPages - StartPageIndex);
int32 EndPageIndex = Algo::UpperBoundBy(RemainingPages, EndTime,
[&ItemStartProjection](const PageType& Page) { return ItemStartProjection(Page.Items[0]); });
// EndPageIndex needs to be remapped back to the full page range
EndPageIndex += StartPageIndex;
// past the end means we still have to search the last page, so no early out here
check(EndPageIndex > 0)
// find the page before the first page that outside the range (thus -1)
EndPageIndex -= 1;
{
const PageType& Page = PageData[EndPageIndex];
TArrayView<ItemType, int32> PageValues(Page.Items, static_cast<int32>(Page.Count));
int32 Index = Algo::UpperBoundBy(PageValues, EndTime, ItemStartProjection);
check(Index >= 0);
// Index may point past the end of the page, that means include last element in result
if (Index >= PageValues.Num())
{
Index = PageValues.Num() - 1;
}
else
{
Index -= 1;
}
Result.Max = EndPageIndex * PageSize + Index;
}
return Result;
}
namespace PagedArrayAlgoImpl
{
/**
* Performs binary search, resulting in position of the first element with projected value >= Value.
*
* @param PagedArray The paged array to search through; must be already sorted by SortPredicate.
* @param Value The value to look for
* @param Projection The functor or data member pointer; called via Invoke to compare to Value.
* @param SortPredicate The predicate for sort comparison; defaults to <.
*
* @returns The position of the first element with projected value >= Value; may be == Num.
*/
template<typename SizeType, typename ItemType, typename PageType, typename ValueType, typename ProjectionType, typename SortPredicateType = TLess<>()>
FORCEINLINE SizeType LowerBoundInternal(const TPagedArray<ItemType, PageType>& PagedArray, const ValueType& Value, ProjectionType Projection, SortPredicateType SortPredicate)
{
TArrayView<const PageType, int32> Pages = MakeArrayView(PagedArray.GetPages(), (int32)PagedArray.NumPages());
// Find the first page with projected value of the first item >= searched Value.
int32 PageIndex = Algo::LowerBoundBy(Pages, Value, [&Projection](const PageType& Page) { return Invoke(Projection, Page.Items[0]); }, SortPredicate);
if (PageIndex == 0)
{
return 0;
}
// Look backward to previous items (with projected value >= searched Value) to find the first one.
SizeType ElementIndex = PageIndex * PagedArray.GetPageSize();
if (ElementIndex > PagedArray.Num())
{
ElementIndex = PagedArray.Num();
}
check(ElementIndex > 0);
for (auto It = PagedArray.GetIteratorFromItem(ElementIndex - 1); It; It.PrevItem())
{
auto&& CheckValue = Invoke(Projection, *(It.GetCurrentItem()));
if (SortPredicate(CheckValue, Value))
{
break;
}
ElementIndex = It.GetCurrentItemIndex();
}
return ElementIndex;
}
/**
* Performs binary search, resulting in position of the first element with projected value > Value.
*
* @param PagedArray The paged array to search through; must be already sorted by SortPredicate.
* @param Value The value to look for
* @param Projection The functor or data member pointer; called via Invoke to compare to Value.
* @param SortPredicate The predicate for sort comparison; defaults to <.
*
* @returns The position of the first element with projected value > Value; may be == Num.
*/
template<typename SizeType, typename ItemType, typename PageType, typename ValueType, typename ProjectionType, typename SortPredicateType = TLess<>()>
FORCEINLINE SizeType UpperBoundInternal(const TPagedArray<ItemType, PageType>& PagedArray, const ValueType& Value, ProjectionType Projection, SortPredicateType SortPredicate)
{
TArrayView<const PageType, int32> Pages = MakeArrayView(PagedArray.GetPages(), (int32)PagedArray.NumPages());
// Find the first page with projected value of the first item > searched Value.
int32 PageIndex = Algo::UpperBoundBy(Pages, Value, [&Projection](const PageType& Page) { return Invoke(Projection, Page.Items[0]); }, SortPredicate);
if (PageIndex == 0)
{
return 0;
}
// Look backward to previous items (with projected value > searched Value) to find the first one.
SizeType ElementIndex = PageIndex * PagedArray.GetPageSize();
if (ElementIndex > PagedArray.Num())
{
ElementIndex = PagedArray.Num();
}
check(ElementIndex > 0);
for (auto It = PagedArray.GetIteratorFromItem(ElementIndex - 1); It; It.PrevItem())
{
auto&& CheckValue = Invoke(Projection, *(It.GetCurrentItem()));
if (!SortPredicate(Value, CheckValue))
{
break;
}
ElementIndex = It.GetCurrentItemIndex();
}
return ElementIndex;
}
/**
* Performs binary search, resulting in position of the first element with projected value closest to Value.
*
* @param PagedArray The paged array to search through; must be already sorted by SortPredicate.
* @param Value The value to look for
* @param Projection The functor or data member pointer; called via Invoke to compare to Value.
* @param SortPredicate The predicate for sort comparison; defaults to <.
*
* @returns The position of the first element with projected value closest to Value; == 0 if Num == 0, otherwise is a value in range [0 .. Num-1].
*/
template<typename SizeType, typename ItemType, typename PageType, typename ValueType, typename ProjectionType, typename SortPredicateType = TLess<>()
UE_REQUIRES(TIsArithmetic<ValueType>::Value)>
FORCEINLINE SizeType BinarySearchClosestInternal(const TPagedArray<ItemType, PageType>& PagedArray, ValueType Value, ProjectionType Projection, SortPredicateType SortPredicate)
{
if (PagedArray.Num() <= 1)
{
return 0;
}
// Find the first page with projected value of the first item >= searched Value.
TArrayView<const PageType, int32> Pages = MakeArrayView(PagedArray.GetPages(), (int32)PagedArray.NumPages());
int32 StartPageIndex = Algo::LowerBoundBy(Pages, Value, [&Projection](const PageType& Page) { return Invoke(Projection, Page.Items[0]); }, SortPredicate);
if (StartPageIndex == 0)
{
return 0;
}
// Iterate backward starting from this item (first item of the found page).
SizeType ClosestElementIndex = StartPageIndex * PagedArray.GetPageSize();
if (ClosestElementIndex >= PagedArray.Num())
{
ClosestElementIndex = PagedArray.Num() - 1;
}
if (ClosestElementIndex > 0)
{
auto&& ClosestElementValue = Invoke(Projection, PagedArray[ClosestElementIndex]);
ValueType ClosestTimeDelta = FMath::Abs(ClosestElementValue - Value);
for (auto It = PagedArray.GetIteratorFromItem(ClosestElementIndex - 1); It; It.PrevItem())
{
auto&& CheckValue = Invoke(Projection, *(It.GetCurrentItem()));
ValueType Delta = FMath::Abs(CheckValue - Value);
if (Delta > ClosestTimeDelta)
{
break;
}
ClosestTimeDelta = Delta;
ClosestElementIndex = It.GetCurrentItemIndex();
}
}
return ClosestElementIndex;
}
} // namespace PagedArrayAlgoImpl
namespace PagedArrayAlgo
{
/**
* Performs binary search, resulting in position of the first element >= Value.
*
* @param PagedArray The paged array to search through; must be already sorted by SortPredicate.
* @param Value The value to look for
* @param SortPredicate Predicate for sort comparison; defaults to <.
*
* @returns Position of the first element >= Value; may be == Num.
*/
template <typename ItemType, typename PageType, typename SortPredicateType>
FORCEINLINE auto LowerBound(const TPagedArray<ItemType, PageType>& PagedArray, const ItemType& Value, SortPredicateType SortPredicate) -> decltype(GetNum(PagedArray))
{
using SizeType = decltype(GetNum(PagedArray));
return PagedArrayAlgoImpl::LowerBoundInternal<SizeType>(PagedArray, Value, FIdentityFunctor(), SortPredicate);
}
template <typename ItemType, typename PageType>
FORCEINLINE auto LowerBound(const TPagedArray<ItemType, PageType>& PagedArray, const ItemType& Value) -> decltype(GetNum(PagedArray))
{
using SizeType = decltype(GetNum(PagedArray));
return PagedArrayAlgoImpl::LowerBoundInternal<SizeType>(PagedArray, Value, FIdentityFunctor(), TLess<>());
}
/**
* Performs binary search, resulting in position of the first element with projected value >= Value.
*
* @param PagedArray The paged array to search through, must be already sorted by SortPredicate.
* @param Value The value to look for
* @param Projection Functor or data member pointer; called via Invoke to compare to Value.
* @param SortPredicate Predicate for sort comparison; defaults to <.
*
* @returns Position of the first element with projected value >= Value; may be == Num.
*/
template <typename ItemType, typename PageType, typename ValueType, typename ProjectionType, typename SortPredicateType>
FORCEINLINE auto LowerBoundBy(const TPagedArray<ItemType, PageType>& PagedArray, const ValueType& Value, ProjectionType Projection, SortPredicateType SortPredicate) -> decltype(GetNum(PagedArray))
{
using SizeType = decltype(GetNum(PagedArray));
return PagedArrayAlgoImpl::LowerBoundInternal<SizeType>(PagedArray, Value, Projection, SortPredicate);
}
template <typename ItemType, typename PageType, typename ValueType, typename ProjectionType>
FORCEINLINE auto LowerBoundBy(const TPagedArray<ItemType, PageType>& PagedArray, const ValueType& Value, ProjectionType Projection) -> decltype(GetNum(PagedArray))
{
using SizeType = decltype(GetNum(PagedArray));
return PagedArrayAlgoImpl::LowerBoundInternal<SizeType>(PagedArray, Value, Projection, TLess<>());
}
/**
* Performs binary search, resulting in position of the first element > Value.
*
* @param PagedArray The paged array to search through, must be already sorted by SortPredicate.
* @param Value The value to look for
* @param SortPredicate Predicate for sort comparison; defaults to <.
*
* @returns Position of the first element > Value; may be == Num.
*/
template <typename ItemType, typename PageType, typename SortPredicateType>
FORCEINLINE auto UpperBound(const TPagedArray<ItemType, PageType>& PagedArray, const ItemType& Value, SortPredicateType SortPredicate) -> decltype(GetNum(PagedArray))
{
using SizeType = decltype(GetNum(PagedArray));
return PagedArrayAlgoImpl::UpperBoundInternal<SizeType>(PagedArray, Value, FIdentityFunctor(), SortPredicate);
}
template <typename ItemType, typename PageType>
FORCEINLINE auto UpperBound(const TPagedArray<ItemType, PageType>& PagedArray, const ItemType& Value) -> decltype(GetNum(PagedArray))
{
using SizeType = decltype(GetNum(PagedArray));
return PagedArrayAlgoImpl::UpperBoundInternal<SizeType>(PagedArray, Value, FIdentityFunctor(), TLess<>());
}
/**
* Performs binary search, resulting in position of the first element with projected value > Value.
*
* @param PagedArray The paged array to search through, must be already sorted by SortPredicate.
* @param Value The value to look for
* @param Projection Functor or data member pointer; called via Invoke to compare to Value.
* @param SortPredicate Predicate for sort comparison; defaults to <.
*
* @returns Position of the first element with projected value > Value; may be == Num.
*/
template <typename ItemType, typename PageType, typename ValueType, typename ProjectionType, typename SortPredicateType>
FORCEINLINE auto UpperBoundBy(const TPagedArray<ItemType, PageType>& PagedArray, const ValueType& Value, ProjectionType Projection, SortPredicateType SortPredicate) -> decltype(GetNum(PagedArray))
{
using SizeType = decltype(GetNum(PagedArray));
return PagedArrayAlgoImpl::UpperBoundInternal<SizeType>(PagedArray, Value, Projection, SortPredicate);
}
template <typename ItemType, typename PageType, typename ValueType, typename ProjectionType>
FORCEINLINE auto UpperBoundBy(const TPagedArray<ItemType, PageType>& PagedArray, const ValueType& Value, ProjectionType Projection) -> decltype(GetNum(PagedArray))
{
using SizeType = decltype(GetNum(PagedArray));
return PagedArrayAlgoImpl::UpperBoundInternal<SizeType>(PagedArray, Value, Projection, TLess<>());
}
/**
* Performs binary search, resulting in position of the first element closest to Value.
*
* @param PagedArray The paged array to search through; must be already sorted by SortPredicate.
* @param Value The value to look for
* @param SortPredicate The predicate for sort comparison; defaults to <.
*
* @returns The position of the first element closest to Value; == 0 if Num == 0, otherwise is a value in range [0 .. Num-1].
*/
template <typename ItemType, typename PageType, typename SortPredicateType>
FORCEINLINE auto BinarySearchClosest(const TPagedArray<ItemType, PageType>& PagedArray, const ItemType& Value, SortPredicateType SortPredicate) -> decltype(GetNum(PagedArray))
{
using SizeType = decltype(GetNum(PagedArray));
return PagedArrayAlgoImpl::BinarySearchClosestInternal<SizeType>(PagedArray, Value, FIdentityFunctor(), SortPredicate);
}
template <typename ItemType, typename PageType>
FORCEINLINE auto BinarySearchClosest(const TPagedArray<ItemType, PageType>& PagedArray, const ItemType& Value) -> decltype(GetNum(PagedArray))
{
using SizeType = decltype(GetNum(PagedArray));
return PagedArrayAlgoImpl::BinarySearchClosestInternal<SizeType>(PagedArray, Value, FIdentityFunctor(), TLess<>());
}
/**
* Performs binary search, resulting in position of the first element with projected value closest to Value.
*
* @param PagedArray The paged array to search through; must be already sorted by SortPredicate.
* @param Value The value to look for
* @param Projection The functor or data member pointer; called via Invoke to compare to Value.
* @param SortPredicate The predicate for sort comparison; defaults to <.
*
* @returns The position of the first element with projected value closest to Value; == 0 if Num == 0, otherwise is a value in range [0 .. Num-1].
*/
template <typename ItemType, typename PageType, typename ValueType, typename ProjectionType, typename SortPredicateType>
FORCEINLINE auto BinarySearchClosestBy(const TPagedArray<ItemType, PageType>& PagedArray, const ValueType& Value, ProjectionType Projection, SortPredicateType SortPredicate) -> decltype(GetNum(PagedArray))
{
using SizeType = decltype(GetNum(PagedArray));
return PagedArrayAlgoImpl::BinarySearchClosestInternal<SizeType>(PagedArray, Value, Projection, SortPredicate);
}
template <typename ItemType, typename PageType, typename ValueType, typename ProjectionType>
FORCEINLINE auto BinarySearchClosestBy(const TPagedArray<ItemType, PageType>& PagedArray, const ValueType& Value, ProjectionType Projection) -> decltype(GetNum(PagedArray))
{
using SizeType = decltype(GetNum(PagedArray));
return PagedArrayAlgoImpl::BinarySearchClosestInternal<SizeType>(PagedArray, Value, Projection, TLess<>());
}
} // namespace PagedArrayAlgo
} // namespace TraceServices