Files
UnrealEngine/Engine/Source/Developer/TraceServices/Private/Model/SbTree.cpp
2025-05-18 13:04:45 +08:00

974 lines
27 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SbTree.h"
#include "Misc/FileHelper.h"
#include "Model/AllocationItem.h"
#include "TraceServices/Containers/Allocators.h"
#include <limits>
namespace TraceServices
{
#define USE_OFFSETTED_CELLS 1
////////////////////////////////////////////////////////////////////////////////////////////////////
// FSbTreeUtils
////////////////////////////////////////////////////////////////////////////////////////////////////
uint32 FSbTreeUtils::GetMaxDepth(uint32 TotalColumns)
{
return 32 - FMath::CountLeadingZeros(TotalColumns);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
uint32 FSbTreeUtils::GetCellAtDepth(uint32 Column, uint32 Depth)
{
// On depth D, the cell indices starts at 2^D-1 and increases by 2^(D+1).
uint32 LeafIndex = Column * 2;
uint32 K = 1 << Depth;
return (LeafIndex & ~K) | (K - 1);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
uint32 FSbTreeUtils::GetCommonDepth(uint32 ColumnA, uint32 ColumnB)
{
uint32 Xor = ColumnA ^ ColumnB;
return FSbTreeUtils::GetMaxDepth(Xor);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
uint32 FSbTreeUtils::GetCellWidth(uint32 CellIndex)
{
return ((CellIndex ^ (CellIndex + 1)) >> 1) + 1;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// FSbTreeCell
////////////////////////////////////////////////////////////////////////////////////////////////////
FSbTreeCell::FSbTreeCell(ILinearAllocator& InAllocator)
: Allocator(InAllocator)
//, Allocs(InAllocator, 1024)
, MinStartEventIndex(std::numeric_limits<uint32>::max())
, MaxEndEventIndex(0)
, MinStartTime(std::numeric_limits<double>::max())
, MaxEndTime(0.0)
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FSbTreeCell::~FSbTreeCell()
{
for (const FAllocationItem* Alloc : Allocs)
{
delete Alloc;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FSbTreeCell::AddAlloc(const FAllocationItem* Alloc)
{
Allocs.Add(Alloc);
if (Alloc->StartEventIndex < MinStartEventIndex)
{
MinStartEventIndex = Alloc->StartEventIndex;
}
if (Alloc->EndEventIndex > MaxEndEventIndex)
{
MaxEndEventIndex = Alloc->EndEventIndex;
}
if (Alloc->StartTime < MinStartTime)
{
MinStartTime = Alloc->StartTime;
}
if (Alloc->EndTime > MaxEndTime)
{
MaxEndTime = Alloc->EndTime;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FSbTreeCell::Query(TArray<const FAllocationItem*>& OutAllocs, const IAllocationsProvider::FQueryParams& Params) const
{
switch (Params.Rule)
{
case IAllocationsProvider::EQueryRule::aAf: // active allocs at A
{
const double Time = Params.TimeA;
for (const FAllocationItem* Alloc : Allocs)
{
if (Alloc->StartTime <= Time && Time <= Alloc->EndTime)
{
OutAllocs.Add(Alloc);
}
}
}
break;
case IAllocationsProvider::EQueryRule::afA: // before
{
const double Time = Params.TimeA;
for (const FAllocationItem* Alloc : Allocs)
{
if (Alloc->EndTime <= Time)
{
OutAllocs.Add(Alloc);
}
}
}
break;
case IAllocationsProvider::EQueryRule::Aaf: // after
{
const double Time = Params.TimeA;
for (const FAllocationItem* Alloc : Allocs)
{
if (Alloc->StartTime >= Time)
{
OutAllocs.Add(Alloc);
}
}
}
break;
case IAllocationsProvider::EQueryRule::aAfB: // decline
{
const double TimeA = Params.TimeA;
const double TimeB = Params.TimeB;
for (const FAllocationItem* Alloc : Allocs)
{
if (Alloc->StartTime <= TimeA && Alloc->EndTime >= TimeA && Alloc->EndTime <= TimeB)
{
OutAllocs.Add(Alloc);
}
}
}
break;
case IAllocationsProvider::EQueryRule::AaBf: // growth
{
const double TimeA = Params.TimeA;
const double TimeB = Params.TimeB;
for (const FAllocationItem* Alloc : Allocs)
{
if (Alloc->StartTime >= TimeA && Alloc->StartTime <= TimeB && Alloc->EndTime >= TimeB)
{
OutAllocs.Add(Alloc);
}
}
}
break;
case IAllocationsProvider::EQueryRule::aAfaBf: // decline + growth
{
const double TimeA = Params.TimeA;
const double TimeB = Params.TimeB;
for (const FAllocationItem* Alloc : Allocs)
{
if ((Alloc->StartTime <= TimeA && Alloc->EndTime >= TimeA && Alloc->EndTime <= TimeB) || // decline
(Alloc->StartTime >= TimeA && Alloc->StartTime <= TimeB && Alloc->EndTime >= TimeB)) // growth
{
OutAllocs.Add(Alloc);
}
}
}
break;
case IAllocationsProvider::EQueryRule::AfB: // free events
{
const double TimeA = Params.TimeA;
const double TimeB = Params.TimeB;
for (const FAllocationItem* Alloc : Allocs)
{
if (Alloc->EndTime >= TimeA && Alloc->EndTime <= TimeB)
{
OutAllocs.Add(Alloc);
}
}
}
break;
case IAllocationsProvider::EQueryRule::AaB: // alloc events
{
const double TimeA = Params.TimeA;
const double TimeB = Params.TimeB;
for (const FAllocationItem* Alloc : Allocs)
{
if (Alloc->StartTime >= TimeA && Alloc->StartTime <= TimeB)
{
OutAllocs.Add(Alloc);
}
}
}
break;
case IAllocationsProvider::EQueryRule::AafB: // short living allocs
{
const double TimeA = Params.TimeA;
const double TimeB = Params.TimeB;
for (const FAllocationItem* Alloc : Allocs)
{
if (Alloc->StartTime >= TimeA && Alloc->EndTime <= TimeB)
{
OutAllocs.Add(Alloc);
}
}
}
break;
case IAllocationsProvider::EQueryRule::aABf: // long living allocs
{
const double TimeA = Params.TimeA;
const double TimeB = Params.TimeB;
for (const FAllocationItem* Alloc : Allocs)
{
if (Alloc->StartTime <= TimeA && Alloc->EndTime >= TimeB)
{
OutAllocs.Add(Alloc);
}
}
}
break;
case IAllocationsProvider::EQueryRule::AaBCf: // memory leaks
{
const double TimeA = Params.TimeA;
const double TimeB = Params.TimeB;
const double TimeC = Params.TimeC;
for (const FAllocationItem* Alloc : Allocs)
{
if (Alloc->StartTime >= TimeA && Alloc->StartTime <= TimeB && Alloc->EndTime >= TimeC)
{
OutAllocs.Add(Alloc);
}
}
}
break;
case IAllocationsProvider::EQueryRule::AaBfC: // limited lifetime
{
const double TimeA = Params.TimeA;
const double TimeB = Params.TimeB;
const double TimeC = Params.TimeC;
for (const FAllocationItem* Alloc : Allocs)
{
if (Alloc->StartTime >= TimeA && Alloc->StartTime <= TimeB && Alloc->EndTime >= TimeB && Alloc->EndTime <= TimeC)
{
OutAllocs.Add(Alloc);
}
}
}
break;
case IAllocationsProvider::EQueryRule::aABfC: // decline of long living allocs
{
const double TimeA = Params.TimeA;
const double TimeB = Params.TimeB;
const double TimeC = Params.TimeC;
for (const FAllocationItem* Alloc : Allocs)
{
if (Alloc->StartTime <= TimeA && Alloc->EndTime >= TimeB && Alloc->EndTime <= TimeC)
{
OutAllocs.Add(Alloc);
}
}
}
break;
case IAllocationsProvider::EQueryRule::AaBCfD: // specific lifetime
{
const double TimeA = Params.TimeA;
const double TimeB = Params.TimeB;
const double TimeC = Params.TimeC;
const double TimeD = Params.TimeD;
for (const FAllocationItem* Alloc : Allocs)
{
if (Alloc->StartTime >= TimeA && Alloc->StartTime <= TimeB && Alloc->EndTime >= TimeC && Alloc->EndTime <= TimeD)
{
OutAllocs.Add(Alloc);
}
}
}
break;
case IAllocationsProvider::EQueryRule::AoB: // paged out allocs
{
const double TimeA = Params.TimeA;
const double TimeB = Params.TimeB;
for (const FAllocationItem* Alloc : Allocs)
{
if ((Alloc->IsSwap() && Alloc->StartTime >= TimeA && Alloc->StartTime <= TimeB) ||
(!Alloc->IsSwap() && Alloc->StartTime <= TimeB && Alloc->EndTime >= TimeA)) // include allocs that were freed between A and B
{
OutAllocs.Add(Alloc);
}
}
}
break;
case IAllocationsProvider::EQueryRule::AiB: // paged in allocs
{
const double TimeA = Params.TimeA;
const double TimeB = Params.TimeB;
for (const FAllocationItem* Alloc : Allocs)
{
if ((Alloc->IsSwap() && Alloc->EndTime >= TimeA && Alloc->EndTime <= TimeB) ||
(!Alloc->IsSwap() && Alloc->StartTime <= TimeB && Alloc->EndTime >= TimeA)) // include allocs that were freed between A and B
{
OutAllocs.Add(Alloc);
}
}
}
break;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// FSbTree
////////////////////////////////////////////////////////////////////////////////////////////////////
FSbTree::FSbTree(ILinearAllocator& InAllocator, uint32 InColumnShift)
: Allocator(InAllocator)
, ColumnShift(InColumnShift)
, CurrentColumn(0)
{
// Add the first cell.
Cells.Add(nullptr);
#if USE_OFFSETTED_CELLS
// Add the first offsetted cell.
OffsettedCells.Add(nullptr);
#endif // USE_OFFSETTED_CELLS
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FSbTree::~FSbTree()
{
for (const FSbTreeCell* CellPtr : Cells)
{
if (CellPtr != nullptr)
{
delete CellPtr;
}
}
Cells.Reset();
#if USE_OFFSETTED_CELLS
for (const FSbTreeCell* CellPtr : OffsettedCells)
{
if (CellPtr != nullptr)
{
delete CellPtr;
}
}
OffsettedCells.Reset();
#endif // USE_OFFSETTED_CELLS
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FSbTree::SetTimeForEvent(uint32 EventIndex, double Time)
{
// Detect the first event of each new column.
if ((EventIndex & ((1 << ColumnShift) - 1)) == 0)
{
if (static_cast<uint32>(ColumnStartTimes.Num()) == (EventIndex >> ColumnShift))
{
ColumnStartTimes.Add(Time);
}
else
{
check(static_cast<uint32>(ColumnStartTimes.Num()) == (EventIndex >> ColumnShift) + 1);
check(ColumnStartTimes.Last() == Time);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FSbTree::AddAlloc(const FAllocationItem* Alloc)
{
check(Alloc != nullptr);
check(Alloc->EndTime >= LastAllocEndTime);
LastAllocEndTime = Alloc->EndTime;
uint32 StartColumn = Alloc->StartEventIndex >> ColumnShift;
uint32 EndColumn = Alloc->EndEventIndex >> ColumnShift;
if (EndColumn > CurrentColumn)
{
check(static_cast<uint32>(Cells.Num()) > (CurrentColumn << 1));
check(static_cast<uint32>(ColumnStartTimes.Num()) == EndColumn + 1);
// Adds 2 cells for each new column.
uint32 CellsToAdd = (EndColumn - CurrentColumn) << 1;
// We are only adding cell slots. A cell is created later, only if we add an alloc to it.
Cells.AddDefaulted(CellsToAdd);
#if USE_OFFSETTED_CELLS
OffsettedCells.AddDefaulted(CellsToAdd);
#endif // USE_OFFSETTED_CELLS
CurrentColumn = EndColumn;
//TODO: "Close" some of the "open" cells.
}
#if USE_OFFSETTED_CELLS
uint32 Depth;
uint32 CellIndex;
bool bUseOffsettedCells;
const uint32 DeltaColumns = EndColumn - StartColumn;
if (DeltaColumns == 0)
{
Depth = 0;
CellIndex = StartColumn << 1;
bUseOffsettedCells = false;
}
else
{
Depth = 32 - FMath::CountLeadingZeros(DeltaColumns);
uint32 HalfCellWidth = (1 << Depth) >> 1;
// In a cell, each alloc has start event column only in the first half of the cell and the end event column in the second half of the cell.
// For a column, the bit indicating the "type of the half cell" at a certain depth ("start columns half cell" or "end columns half cell")
// is Column & HalfCellWidth. This is also true for offsetted cells (just that for normal cells 0 means "start columns half cell"
// while in offsetted cells 1 means "start columns half cell").
// If the "type of the half cell" bit is the same for StartColumn and for EndColumn on the min depth (computed based on the column width for this alloc)
// it means that the alloc doesn't fit in the (alligned) cells on this depth (neither in offsetted cells on this depth).
// Those allocs are pushed to the next depth.
if ((StartColumn & HalfCellWidth) == (EndColumn & HalfCellWidth))
{
++Depth;
HalfCellWidth <<= 1;
}
CellIndex = FSbTreeUtils::GetCellAtDepth(StartColumn & ~HalfCellWidth, Depth);
bUseOffsettedCells = ((StartColumn & HalfCellWidth) != 0);
}
#else // USE_OFFSETTED_CELLS
const uint32 Depth = FSbTreeUtils::GetCommonDepth(StartColumn, EndColumn);
const uint32 CellIndex = FSbTreeUtils::GetCellAtDepth(StartColumn, Depth);
const bool bUseOffsettedCells = false;
#endif // USE_OFFSETTED_CELLS
FSbTreeCell* CellPtr;
if (!bUseOffsettedCells)
{
check(CellIndex < static_cast<uint32>(Cells.Num()));
CellPtr = Cells[CellIndex];
if (CellPtr == nullptr)
{
// Create new cell.
CellPtr = new FSbTreeCell(Allocator);
Cells[CellIndex] = CellPtr;
}
}
#if USE_OFFSETTED_CELLS
else
{
check((CellIndex & 1) != 0); // depth 0 doesn't use offsetted cells
check(CellIndex < static_cast<uint32>(OffsettedCells.Num()));
CellPtr = OffsettedCells[CellIndex];
if (CellPtr == nullptr)
{
// Create new cell.
CellPtr = new FSbTreeCell(Allocator);
OffsettedCells[CellIndex] = CellPtr;
}
}
#endif // USE_OFFSETTED_CELLS
CellPtr->AddAlloc(Alloc);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
int32 FSbTree::GetColumnsAtTime(double Time, int32* OutStartColumn, int32* OutEndColumn) const
{
// Returns the first column with T >= Time.
// [0 .. N]
const int32 LowerBoundColumn = Algo::LowerBound(ColumnStartTimes, Time);
if (OutStartColumn)
{
*OutStartColumn = LowerBoundColumn - 1; // [-1 .. N-1]
}
if (OutEndColumn)
{
int32 Column = LowerBoundColumn - 1; // [-1 .. N-1]
const int32 Last = ColumnStartTimes.Num() - 1; // N-1
while (Column < Last && ColumnStartTimes[Column + 1] == Time)
{
++Column;
}
*OutEndColumn = Column; // [-1 .. N-1]
}
return LowerBoundColumn;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FSbTree::Query(TArray<const FSbTreeCell*>& OutCells, const IAllocationsProvider::FQueryParams& Params) const
{
switch (Params.Rule)
{
case IAllocationsProvider::EQueryRule::aAf: // active allocs at A
{
int32 Column1, Column2;
GetColumnsAtTime(Params.TimeA, &Column1, &Column2);
IterateCells(OutCells, Column1, Column2);
}
break;
case IAllocationsProvider::EQueryRule::afA: // before
{
int32 Column2;
GetColumnsAtTime(Params.TimeA, nullptr, &Column2);
IterateCells(OutCells, 0, Column2);
}
break;
case IAllocationsProvider::EQueryRule::Aaf: // after
{
int32 Column1;
GetColumnsAtTime(Params.TimeA, &Column1, nullptr);
IterateCells(OutCells, Column1, CurrentColumn);
}
break;
case IAllocationsProvider::EQueryRule::aAfB: // decline
{
int32 Column1;
GetColumnsAtTime(Params.TimeA, &Column1, nullptr);
int32 Column2;
GetColumnsAtTime(Params.TimeB, nullptr, &Column2);
IterateCells(OutCells, Column1, Column2);
}
break;
case IAllocationsProvider::EQueryRule::AaBf: // growth
{
int32 Column1;
GetColumnsAtTime(Params.TimeB, &Column1, nullptr);
IterateCells(OutCells, Column1, CurrentColumn);
}
break;
case IAllocationsProvider::EQueryRule::aAfaBf: // decline + growth
{
int32 Column1;
GetColumnsAtTime(Params.TimeA, &Column1, nullptr);
IterateCells(OutCells, Column1, CurrentColumn);
}
break;
case IAllocationsProvider::EQueryRule::AfB: // free events
{
int32 Column1;
GetColumnsAtTime(Params.TimeA, &Column1, nullptr);
int32 Column2;
GetColumnsAtTime(Params.TimeB, nullptr, &Column2);
IterateCells(OutCells, Column1, Column2);
}
break;
case IAllocationsProvider::EQueryRule::AaB: // alloc events
{
int32 Column1;
GetColumnsAtTime(Params.TimeA, &Column1, nullptr);
IterateCells(OutCells, Column1, CurrentColumn);
}
break;
case IAllocationsProvider::EQueryRule::AafB: // short living allocs
{
int32 Column1;
GetColumnsAtTime(Params.TimeA, &Column1, nullptr);
int32 Column2;
GetColumnsAtTime(Params.TimeB, nullptr, &Column2);
IterateCells(OutCells, Column1, Column2);
}
break;
case IAllocationsProvider::EQueryRule::aABf: // long living allocs
{
int32 Column1;
GetColumnsAtTime(Params.TimeB, &Column1, nullptr);
IterateCells(OutCells, Column1, CurrentColumn);
}
break;
case IAllocationsProvider::EQueryRule::AaBCf: // memory leaks
{
int32 Column1;
GetColumnsAtTime(Params.TimeC, &Column1, nullptr);
IterateCells(OutCells, Column1, CurrentColumn);
}
break;
case IAllocationsProvider::EQueryRule::AaBfC: // limited lifetime
{
int32 Column1;
GetColumnsAtTime(Params.TimeB, &Column1, nullptr);
int32 Column2;
GetColumnsAtTime(Params.TimeC, nullptr, &Column2);
IterateCells(OutCells, Column1, Column2);
}
break;
case IAllocationsProvider::EQueryRule::aABfC: // decline of long living allocs
{
int32 Column1;
GetColumnsAtTime(Params.TimeB, &Column1, nullptr);
int32 Column2;
GetColumnsAtTime(Params.TimeC, nullptr, &Column2);
IterateCells(OutCells, Column1, Column2);
}
break;
case IAllocationsProvider::EQueryRule::AaBCfD: // specific lifetime
{
int32 Column1;
GetColumnsAtTime(Params.TimeC, &Column1, nullptr);
int32 Column2;
GetColumnsAtTime(Params.TimeD, nullptr, &Column2);
IterateCells(OutCells, Column1, Column2);
}
break;
case IAllocationsProvider::EQueryRule::AoB:
{
int32 Column1;
GetColumnsAtTime(Params.TimeB, nullptr, &Column1); // we care about all allocs up to Time B
IterateCells(OutCells, 0, Column1);
}
break;
case IAllocationsProvider::EQueryRule::AiB:
{
int32 Column1;
GetColumnsAtTime(Params.TimeB, nullptr, &Column1); // we care about all allocs up to Time B
IterateCells(OutCells, 0, Column1);
}
break;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FSbTree::IterateCells(TArray<const FSbTreeCell*>& OutCells, int32 Column) const
{
if (Column < 0 || Column > (int32)CurrentColumn)
{
// not an error; just early out
return;
}
const uint32 LocalColumn = static_cast<uint32>(Column);
const uint32 MaxDepth = FSbTreeUtils::GetMaxDepth(CurrentColumn);
const uint32 NumCells = static_cast<uint32>(Cells.Num());
for (uint32 Depth = 0; Depth <= MaxDepth; ++Depth)
{
const uint32 CellIndex = FSbTreeUtils::GetCellAtDepth(LocalColumn, Depth);
if (CellIndex < NumCells)
{
const FSbTreeCell* CellPtr = Cells[CellIndex];
if (CellPtr)
{
OutCells.Add(CellPtr);
}
}
}
#if USE_OFFSETTED_CELLS
const uint32 NumOffsettedCells = static_cast<uint32>(OffsettedCells.Num());
for (uint32 Depth = 1; Depth <= MaxDepth; ++Depth) // offsetted cells doesn't exist on Depth 0
{
const uint32 HalfCellWidth = (1 << Depth) >> 1;
if (HalfCellWidth > LocalColumn)
{
break;
}
const uint32 CellIndex = FSbTreeUtils::GetCellAtDepth(LocalColumn - HalfCellWidth, Depth);
if (CellIndex < NumOffsettedCells)
{
const FSbTreeCell* CellPtr = OffsettedCells[CellIndex];
if (CellPtr)
{
OutCells.Add(CellPtr);
}
}
}
#endif // USE_OFFSETTED_CELLS
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FSbTree::IterateCells(TArray<const FSbTreeCell*> & OutCells, int32 StartColumn, int32 EndColumn) const
{
if (StartColumn == EndColumn)
{
IterateCells(OutCells, StartColumn);
return;
}
if (EndColumn < 0 || StartColumn > (int32)CurrentColumn || StartColumn > EndColumn)
{
// not an error; just early out
return;
}
const uint32 LocalStartColumn = (StartColumn < 0) ? 0 : static_cast<uint32>(StartColumn);
const uint32 LocalEndColumn = (static_cast<uint32>(EndColumn) > CurrentColumn) ? CurrentColumn : static_cast<uint32>(EndColumn);
check(LocalStartColumn <= LocalEndColumn);
const uint32 MaxDepth = FSbTreeUtils::GetMaxDepth(CurrentColumn);
const uint32 NumCells = static_cast<uint32>(Cells.Num());
for (uint32 Depth = 0; Depth <= MaxDepth; ++Depth)
{
const uint32 FirstCellIndex = FSbTreeUtils::GetCellAtDepth(LocalStartColumn, Depth);
const uint32 LastCellIndex = FMath::Min(FSbTreeUtils::GetCellAtDepth(LocalEndColumn, Depth) + 1, NumCells);
const uint32 CellIncrement = 1 << (Depth + 1);
for (uint32 CellIndex = FirstCellIndex; CellIndex < LastCellIndex; CellIndex += CellIncrement)
{
const FSbTreeCell* CellPtr = Cells[CellIndex];
if (CellPtr)
{
OutCells.Add(CellPtr);
}
}
}
#if USE_OFFSETTED_CELLS
const uint32 NumOffsettedCells = static_cast<uint32>(OffsettedCells.Num());
for (uint32 Depth = 1; Depth <= MaxDepth; ++Depth) // offsetted cells doesn't exist on Depth 0
{
const uint32 HalfCellWidth = (1 << Depth) >> 1;
const uint32 OffsettedStartColumn = (LocalStartColumn > HalfCellWidth) ? LocalStartColumn - HalfCellWidth : 0;
const uint32 FirstCellIndex = FSbTreeUtils::GetCellAtDepth(OffsettedStartColumn, Depth);
const uint32 OffsettedEndColumn = (LocalEndColumn > HalfCellWidth) ? LocalEndColumn - HalfCellWidth : 0;
const uint32 LastCellIndex = FMath::Min(FSbTreeUtils::GetCellAtDepth(OffsettedEndColumn, Depth) + 1, NumOffsettedCells);
const uint32 CellIncrement = 1 << (Depth + 1);
for (uint32 CellIndex = FirstCellIndex; CellIndex < LastCellIndex; CellIndex += CellIncrement)
{
const FSbTreeCell* CellPtr = OffsettedCells[CellIndex];
if (CellPtr)
{
OutCells.Add(CellPtr);
}
}
}
#endif // USE_OFFSETTED_CELLS
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FSbTree::DebugPrint() const
{
uint32 TotalColumns = CurrentColumn + 1;
uint32 MaxDepth = FSbTreeUtils::GetMaxDepth(TotalColumns);
uint32 TotalAllocs = 0;
uint32 LocalMaxAllocCountPerCell = 0;
uint32 NotEmptyCellCount = 0;
const uint32 CellCount = static_cast<uint32>(Cells.Num());
for (uint32 CellIndex = 0; CellIndex < CellCount; ++CellIndex)
{
const FSbTreeCell* CellPtr = Cells[CellIndex];
if (CellPtr != nullptr)
{
++NotEmptyCellCount;
const uint32 CellAllocCount = CellPtr->GetAllocCount();
TotalAllocs += CellAllocCount;
if (CellAllocCount > LocalMaxAllocCountPerCell)
{
LocalMaxAllocCountPerCell = CellAllocCount;
}
}
}
uint32 TotalCells = CellCount;
#if USE_OFFSETTED_CELLS
uint32 NotEmptyOffsettedCellCount = 0;
const uint32 OffsettedCellCount = static_cast<uint32>(OffsettedCells.Num());
for (uint32 CellIndex = 0; CellIndex < OffsettedCellCount; ++CellIndex)
{
const FSbTreeCell* CellPtr = OffsettedCells[CellIndex];
if (CellPtr != nullptr)
{
++NotEmptyOffsettedCellCount;
const uint32 CellAllocCount = CellPtr->GetAllocCount();
TotalAllocs += CellAllocCount;
if (CellAllocCount > LocalMaxAllocCountPerCell)
{
LocalMaxAllocCountPerCell = CellAllocCount;
}
}
}
TotalCells += OffsettedCellCount;
#endif // USE_OFFSETTED_CELLS
//#define SbTreePrint(x) FPlatformMisc::LowLevelOutputDebugString(TEXT(x))
//#define SbTreePrintF(fmt, ...) FPlatformMisc::LowLevelOutputDebugStringf(TEXT(fmt), __VA_ARGS__)
FString StringBuffer;
#define SbTreePrint(x) StringBuffer += TEXT(x)
#define SbTreePrintF(fmt, ...) StringBuffer.Appendf(TEXT(fmt), __VA_ARGS__)
SbTreePrintF("Column Width:\t%u\n", (1 << ColumnShift));
SbTreePrintF("Allocs:\t%u\n", TotalAllocs);
SbTreePrintF("Columns:\t%u\n", TotalColumns);
SbTreePrintF("Max Depth:\t%u\n", MaxDepth);
SbTreePrintF("Cells:\t%u\n", TotalCells);
SbTreePrintF("Max Alloc Count Per Cell:\t%u\n", LocalMaxAllocCountPerCell);
SbTreePrintF("Not Empty Cells:\t%u\n", NotEmptyCellCount);
#if USE_OFFSETTED_CELLS
SbTreePrintF("Not Empty Offsetted Cells:\t%u\n", NotEmptyOffsettedCellCount);
#endif // USE_OFFSETTED_CELLS
SbTreePrint("\n");
for (uint32 Depth = 0; Depth <= MaxDepth; ++Depth)
{
#if USE_OFFSETTED_CELLS
SbTreePrintF("\t%u\t%u*", Depth, Depth);
#else // USE_OFFSETTED_CELLS
SbTreePrintF("\t%u", Depth);
#endif // USE_OFFSETTED_CELLS
}
SbTreePrint("\n");
TArray<uint32> PrevCellIndex;
PrevCellIndex.AddDefaulted(MaxDepth + 1);
PrevCellIndex[0] = 1;
for (uint32 Column = 0; Column < TotalColumns; ++Column)
{
SbTreePrintF("%u", Column);
for (uint32 Depth = 0; Depth <= MaxDepth; ++Depth)
{
uint32 CellIndex = FSbTreeUtils::GetCellAtDepth(Column, Depth);
if (CellIndex != PrevCellIndex[Depth])
{
const FSbTreeCell* CellPtr = (CellIndex < CellCount) ? Cells[CellIndex] : nullptr;
uint32 AllocCount = (CellPtr != nullptr) ? CellPtr->GetAllocCount() : 0;
#if USE_OFFSETTED_CELLS
const FSbTreeCell* OffsettedCellPtr = (CellIndex < OffsettedCellCount) ? OffsettedCells[CellIndex] : nullptr;
uint32 OffsettedAllocCount = (OffsettedCellPtr != nullptr) ? OffsettedCellPtr->GetAllocCount() : 0;
SbTreePrintF("\t%u\t%u", AllocCount, OffsettedAllocCount);
#else // USE_OFFSETTED_CELLS
SbTreePrintF("\t%u", AllocCount);
#endif // USE_OFFSETTED_CELLS
PrevCellIndex[Depth] = CellIndex;
}
else
{
SbTreePrint("\t\t");
}
}
SbTreePrint("\n");
}
SbTreePrint("\n");
FString FilePath = TEXT("D:/work/sbif.tab");
FFileHelper::SaveStringToFile(StringBuffer, *FilePath);
#undef SbTreePrint
#undef SbTreePrintF
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FSbTree::Validate() const
{
const uint32 MaxDepth = FSbTreeUtils::GetMaxDepth(CurrentColumn);
const uint32 NumCells = static_cast<uint32>(Cells.Num());
for (uint32 Depth = 0; Depth <= MaxDepth; ++Depth)
{
uint32 EventIndex = 0;
double Time = 0.0;
const uint32 CellIncrement = 1 << (Depth + 1);
uint32 CellIndex = FSbTreeUtils::GetCellAtDepth(0, Depth);
while (CellIndex < NumCells)
{
const FSbTreeCell* CellPtr = Cells[CellIndex];
if (CellPtr)
{
check(CellPtr->GetMaxEndEventIndex() > CellPtr->GetMinStartEventIndex());
check(CellPtr->GetMinStartEventIndex() >= EventIndex);
EventIndex = CellPtr->GetMaxEndEventIndex() + 1;
check(CellPtr->GetMaxEndTime() >= CellPtr->GetMinStartTime());
check(CellPtr->GetMinStartTime() >= Time);
Time = CellPtr->GetMinStartTime();
}
CellIndex += CellIncrement;
}
}
#if USE_OFFSETTED_CELLS
const uint32 NumOffsettedCells = static_cast<uint32>(OffsettedCells.Num());
for (uint32 Depth = 0; Depth <= MaxDepth; ++Depth)
{
uint32 EventIndex = 0;
double Time = 0.0;
const uint32 CellIncrement = 1 << (Depth + 1);
uint32 CellIndex = FSbTreeUtils::GetCellAtDepth(0, Depth);
while (CellIndex < NumOffsettedCells)
{
const FSbTreeCell* CellPtr = OffsettedCells[CellIndex];
if (CellPtr)
{
check(CellPtr->GetMaxEndEventIndex() > CellPtr->GetMinStartEventIndex());
check(CellPtr->GetMinStartEventIndex() >= EventIndex);
EventIndex = CellPtr->GetMaxEndEventIndex() + 1;
check(CellPtr->GetMaxEndTime() >= CellPtr->GetMinStartTime());
check(CellPtr->GetMinStartTime() >= Time);
Time = CellPtr->GetMinStartTime();
}
CellIndex += CellIncrement;
}
}
check(NumCells == NumOffsettedCells);
#endif // USE_OFFSETTED_CELLS
}
////////////////////////////////////////////////////////////////////////////////////////////////////
#undef USE_OFFSETTED_CELLS
} // namespace TraceServices