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

962 lines
26 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SCollisionAnalyzer.h"
#include "SlateOptMacros.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Input/SEditableTextBox.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Layout/SSplitter.h"
#include "Widgets/Input/SCheckBox.h"
#include "CollisionAnalyzerStyle.h"
#include "SCAQueryTableRow.h"
#include "SCAQueryDetails.h"
#include "DesktopPlatformModule.h"
#define LOCTEXT_NAMESPACE "SCollisionAnalyzer"
static const int32 NumDrawRecentQueries = 10;
// Column names
const FName SCollisionAnalyzer::IDColumnName(TEXT("ID"));
const FName SCollisionAnalyzer::FrameColumnName(TEXT("Frame"));
const FName SCollisionAnalyzer::TypeColumnName(TEXT("Type"));
const FName SCollisionAnalyzer::ShapeColumnName(TEXT("Shape"));
const FName SCollisionAnalyzer::ModeColumnName(TEXT("Mode"));
const FName SCollisionAnalyzer::TagColumnName(TEXT("Tag"));
const FName SCollisionAnalyzer::OwnerColumnName(TEXT("Owner"));
const FName SCollisionAnalyzer::NumBlockColumnName(TEXT("NumBlock"));
const FName SCollisionAnalyzer::NumTouchColumnName(TEXT("NumTouch"));
const FName SCollisionAnalyzer::TimeColumnName(TEXT("Time"));
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SCollisionAnalyzer::Construct(const FArguments& InArgs, FCollisionAnalyzer* InAnalyzer)
{
Analyzer = InAnalyzer;
bDrawRecentQueries = false;
FrameFilterNum = INDEX_NONE;
MinCPUFilterTime = -1.f;
GroupBy = EQueryGroupMode::Ungrouped;
SortBy = EQuerySortMode::ByID;
SortDirection = EColumnSortMode::Descending;
TotalNumQueries = 0;
ChildSlot
[
SNew(SVerticalBox)
// Toolbar
+SVerticalBox::Slot()
.AutoHeight()
[
SNew( SBorder )
.BorderImage( FCollisionAnalyzerStyle::Get()->GetBrush( "ToolPanel.GroupBorder" ) )
[
SNew(SHorizontalBox)
// Record button
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(1)
[
SNew(SButton)
.ButtonStyle(FCollisionAnalyzerStyle::Get(), "CommonButton")
.OnClicked(this, &SCollisionAnalyzer::OnRecordButtonClicked)
.ToolTipText(LOCTEXT("CAnalyzerRecord_Tooltip", "Start/stop recording collision data"))
.Content()
[
SNew(SImage)
.Image(this, &SCollisionAnalyzer::GetRecordButtonBrush)
]
]
// 'Draw most recent' toggle button
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(1)
[
SNew(SCheckBox)
.Style(FCollisionAnalyzerStyle::Get(), "ToggleButtonCheckbox")
.ToolTipText(LOCTEXT("CAnalyzerToggleRecent_Tooltip", "Toggles drawing of recent collision data"))
.OnCheckStateChanged(this, &SCollisionAnalyzer::OnDrawRecentChanged)
.IsChecked(this, &SCollisionAnalyzer::GetDrawRecentState)
.Content()
[
SNew(SImage)
.Image(FCollisionAnalyzerStyle::Get()->GetBrush("CollisionAnalyzer.ShowRecent"))
]
]
// Load profile
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(1)
[
SNew(SButton)
.ButtonStyle(FCollisionAnalyzerStyle::Get(), "CommonButton")
.OnClicked(this, &SCollisionAnalyzer::OnLoadButtonClicked)
.ToolTipText(LOCTEXT("CAnalyzerLoad_Tooltip", "Load a previously recorded collision data session"))
.Content()
[
SNew(SImage)
.Image(FCollisionAnalyzerStyle::Get()->GetBrush("CollisionAnalyzer.Load"))
]
]
// Save profile
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(1)
[
SNew(SButton)
.ButtonStyle(FCollisionAnalyzerStyle::Get(), "CommonButton")
.OnClicked(this, &SCollisionAnalyzer::OnSaveButtonClicked)
.ToolTipText(LOCTEXT("CAnalyzerSave_Tooltip", "Save the current recorded collision data"))
.Content()
[
SNew(SImage)
.Image(FCollisionAnalyzerStyle::Get()->GetBrush("CollisionAnalyzer.Save"))
]
]
]
]
// List area
+SVerticalBox::Slot()
.FillHeight(1)
[
SNew(SSplitter)
.Orientation(EOrientation::Orient_Vertical)
+SSplitter::Slot()
.Value(2)
[
SNew(SBorder)
.BorderImage(FCollisionAnalyzerStyle::Get()->GetBrush("Menu.Background"))
.Padding(1.0)
[
SAssignNew(QueryTreeWidget, STreeView< TSharedPtr<FQueryTreeItem> >)
.TreeItemsSource(&GroupedQueries)
.SelectionMode(ESelectionMode::Multi)
.OnGenerateRow(this, &SCollisionAnalyzer::QueryTreeGenerateRow)
.OnSelectionChanged(this, &SCollisionAnalyzer::QueryTreeSelectionChanged)
.OnGetChildren(this, &SCollisionAnalyzer::OnGetChildrenForQueryGroup)
.HeaderRow(
SNew(SHeaderRow)
// ID
+SHeaderRow::Column(IDColumnName)
.SortMode(this, &SCollisionAnalyzer::GetIDSortMode)
.OnSort(this, &SCollisionAnalyzer::OnSortByChanged)
.HAlignCell(HAlign_Left)
.FixedWidth(48)
[
SNew(STextBlock)
.Text(LOCTEXT("QueryListIdHeader", "ID"))
]
// Frame number
+SHeaderRow::Column(FrameColumnName)
.FixedWidth(48)
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.Text(LOCTEXT("QueryListFrameHeader", "Frame"))
]
+SVerticalBox::Slot()
.AutoHeight()
.Padding(0,2)
[
SNew(SHorizontalBox)
// Filter entry
+SHorizontalBox::Slot()
.FillWidth(1)
[
SAssignNew(FrameFilterBox, SEditableTextBox)
.SelectAllTextWhenFocused(true)
.OnTextCommitted(this, &SCollisionAnalyzer::FilterTextCommitted)
]
// Group toggle
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SCheckBox)
.Style(FCollisionAnalyzerStyle::Get(), "ToggleButtonCheckbox")
.OnCheckStateChanged(this, &SCollisionAnalyzer::OnGroupByFrameChanged)
.IsChecked( this, &SCollisionAnalyzer::GetGroupByFrameState)
.Content()
[
SNew(SImage)
.Image(FCollisionAnalyzerStyle::Get()->GetBrush("CollisionAnalyzer.Group"))
]
]
]
]
// Type
+SHeaderRow::Column(TypeColumnName)
.FillWidth(0.5)
[
SNew(STextBlock)
.Text(LOCTEXT("QueryListTypeHeader", "Type"))
]
// Shape
+SHeaderRow::Column(ShapeColumnName)
.FillWidth(0.5)
[
SNew(STextBlock)
.Text(LOCTEXT("QueryListShapeHeader", "Shape"))
]
// Shape
+SHeaderRow::Column(ModeColumnName)
.FillWidth(0.5)
[
SNew(STextBlock)
.Text(LOCTEXT("QueryListModeHeader", "Mode"))
]
// Tag
+SHeaderRow::Column(TagColumnName)
.FillWidth(1.5)
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.Text(LOCTEXT("QueryListTagHeader", "Tag"))
]
+SVerticalBox::Slot()
.AutoHeight()
.Padding(0,2)
[
SNew(SHorizontalBox)
// Filter entry
+SHorizontalBox::Slot()
.FillWidth(1)
[
SAssignNew(TagFilterBox, SEditableTextBox)
.SelectAllTextWhenFocused(true)
.OnTextCommitted(this, &SCollisionAnalyzer::FilterTextCommitted)
]
// Group toggle
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SCheckBox)
.Style(FCollisionAnalyzerStyle::Get(), "ToggleButtonCheckbox")
.OnCheckStateChanged(this, &SCollisionAnalyzer::OnGroupByTagChanged)
.IsChecked( this, &SCollisionAnalyzer::GetGroupByTagState)
.Content()
[
SNew(SImage)
.Image(FCollisionAnalyzerStyle::Get()->GetBrush("CollisionAnalyzer.Group"))
]
]
]
]
// Owner
+SHeaderRow::Column(OwnerColumnName)
.FillWidth(1.5)
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.Text(LOCTEXT("QueryListOwnerHeader", "Owner"))
]
+SVerticalBox::Slot()
.AutoHeight()
.Padding(0,2)
[
SNew(SHorizontalBox)
// Filter entry
+SHorizontalBox::Slot()
.FillWidth(1)
[
SAssignNew(OwnerFilterBox, SEditableTextBox)
.SelectAllTextWhenFocused(true)
.OnTextCommitted(this, &SCollisionAnalyzer::FilterTextCommitted)
]
// Group toggle
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SCheckBox)
.Style(FCollisionAnalyzerStyle::Get(), "ToggleButtonCheckbox")
.OnCheckStateChanged(this, &SCollisionAnalyzer::OnGroupByOwnerChanged)
.IsChecked( this, &SCollisionAnalyzer::GetGroupByOwnerState)
.Content()
[
SNew(SImage)
.Image(FCollisionAnalyzerStyle::Get()->GetBrush("CollisionAnalyzer.Group"))
]
]
]
]
// Num blocking hits
+SHeaderRow::Column(NumBlockColumnName)
.FixedWidth(24)
[
SNew(STextBlock)
.Text( LOCTEXT("NumberOfBlockColumnHeader", "#B") )
.ToolTipText( LOCTEXT("NumberBlocksTooltip", "Number of blocking results, red means 'started penetrating'") )
]
// Num touching hits
+SHeaderRow::Column(NumTouchColumnName)
.FixedWidth(24)
[
SNew(STextBlock)
.Text( LOCTEXT("NumberOfTouchesColumnHeader", "#T") )
.ToolTipText( LOCTEXT("NumberTouchTooltip", "Number of touching results") )
]
// CPU time
+SHeaderRow::Column(TimeColumnName)
.SortMode(this, &SCollisionAnalyzer::GetTimeSortMode)
.OnSort(this, &SCollisionAnalyzer::OnSortByChanged)
.FixedWidth(48)
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.Text( LOCTEXT("QueryMillisecondsColumnHeader", "ms") )
.ToolTipText( LOCTEXT("TimeTooltip", "How long this query took, in ms") )
]
+SVerticalBox::Slot()
.AutoHeight()
.Padding(0,2)
[
SAssignNew(TimeFilterBox, SEditableTextBox)
.SelectAllTextWhenFocused(true)
.OnTextCommitted(this, &SCollisionAnalyzer::FilterTextCommitted)
]
]
)
]
]
+SSplitter::Slot()
.Value(1)
[
SAssignNew(QueryDetailsWidget, SCAQueryDetails, SharedThis(this))
]
]
// Status area
+SVerticalBox::Slot()
.AutoHeight()
[
SNew( SBorder )
.BorderImage(FCollisionAnalyzerStyle::Get()->GetBrush("ToolPanel.GroupBorder"))
[
SNew(STextBlock)
.Text(this, &SCollisionAnalyzer::GetStatusText)
]
]
];
Analyzer->OnQueriesChanged().AddSP(this, &SCollisionAnalyzer::OnQueriesChanged);
Analyzer->OnQueryAdded().AddSP(this, &SCollisionAnalyzer::OnQueryAdded);
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
SCollisionAnalyzer::~SCollisionAnalyzer()
{
Analyzer->OnQueriesChanged().RemoveAll(this);
Analyzer->OnQueryAdded().RemoveAll(this);
}
void SCollisionAnalyzer::OnQueriesChanged()
{
RebuildFilteredList();
UpdateDrawnQueries();
}
void SCollisionAnalyzer::OnQueryAdded()
{
const int32 NewQueryIndex = Analyzer->Queries.Num()-1;
const FCAQuery& NewQuery = Analyzer->Queries[NewQueryIndex];
if(ShouldDisplayQuery(NewQuery))
{
// Passed filter so add to filtered results
AddQueryToGroupedQueries(NewQueryIndex, true);
}
QueryTreeWidget->RequestTreeRefresh();
UpdateDrawnQueries();
}
void SCollisionAnalyzer::UpdateDrawnQueries()
{
// First empty 'draw set'
Analyzer->DrawQueryIndices.Empty();
// Add those that are selected
TArray< TSharedPtr<FQueryTreeItem> > SelectedItems = QueryTreeWidget->GetSelectedItems();
for(int32 i=0; i<SelectedItems.Num(); i++)
{
TSharedPtr<FQueryTreeItem> Item = SelectedItems[i];
if(!Item->bIsGroup)
{
Analyzer->DrawQueryIndices.Add(Item->QueryIndex);
}
}
// If selected, draw the most recent NumDrawRecentQueries filtered queries
if(bDrawRecentQueries)
{
for(int32 i=0; i<RecentQueries.Num(); i++)
{
Analyzer->DrawQueryIndices.Add(RecentQueries[i]);
}
}
}
//////////////////////////////////////////////////////////////////////////
// Query Tree
TSharedRef<ITableRow> SCollisionAnalyzer::QueryTreeGenerateRow(TSharedPtr<FQueryTreeItem> InItem, const TSharedRef<STableViewBase>& OwnerTable)
{
return SNew(SCAQueryTableRow, OwnerTable)
.Item(InItem)
.OwnerAnalyzerWidget(SharedThis(this));
}
void SCollisionAnalyzer::QueryTreeSelectionChanged(TSharedPtr<FQueryTreeItem> SelectedItem, ESelectInfo::Type SelectInfo)
{
UpdateDrawnQueries();
// If selecting a single non-group item
TArray< TSharedPtr<FQueryTreeItem> > SelectedItems = QueryTreeWidget->GetSelectedItems();
if(SelectedItems.Num() == 1)
{
TSharedPtr<FQueryTreeItem> Item = SelectedItems[0];
if(!Item->bIsGroup)
{
FCAQuery& ShowQuery = Analyzer->Queries[Item->QueryIndex];
QueryDetailsWidget->SetCurrentQuery(ShowQuery);
}
else
{
QueryDetailsWidget->ClearCurrentQuery();
}
}
else
{
QueryDetailsWidget->ClearCurrentQuery();
}
}
void SCollisionAnalyzer::OnGetChildrenForQueryGroup( TSharedPtr<FQueryTreeItem> InItem, TArray<TSharedPtr<FQueryTreeItem> >& OutChildren )
{
if(InItem->bIsGroup)
{
OutChildren = InItem->QueriesInGroup;
}
}
bool SCollisionAnalyzer::ShouldDisplayQuery(const FCAQuery& Query)
{
// Check frame number filter
if( FrameFilterNum != INDEX_NONE &&
Query.FrameNum != FrameFilterNum )
{
return false;
}
// Check tag filter
if( TagFilterString.Len() > 0 &&
!Query.Params.TraceTag.ToString().Contains(TagFilterString))
{
return false;
}
// Check owner filter
if( OwnerFilterString.Len() > 0 &&
!Query.Params.OwnerTag.ToString().Contains(OwnerFilterString))
{
return false;
}
// Check query time
if( MinCPUFilterTime > 0.f && Query.CPUTime < MinCPUFilterTime )
{
return false;
}
return true;
}
void SCollisionAnalyzer::UpdateFilterInfo()
{
// Get frame filter
FrameFilterNum = INDEX_NONE;
if(FrameFilterBox->GetText().ToString().Len() > 0)
{
FrameFilterNum = FCString::Atoi( *FrameFilterBox->GetText().ToString() );
}
// Get tag filter
TagFilterString = TagFilterBox->GetText().ToString();
// Get owner filter
OwnerFilterString = OwnerFilterBox->GetText().ToString();
MinCPUFilterTime = -1.f;
FString TimeFilterString = TimeFilterBox->GetText().ToString();
if(TimeFilterString.Len() > 0)
{
MinCPUFilterTime = FCString::Atof(*TimeFilterString);
}
}
TSharedPtr<FQueryTreeItem> SCollisionAnalyzer::FindQueryGroup(FName InGroupName, int32 InFrameNum)
{
for(int i=0; i<GroupedQueries.Num(); i++)
{
TSharedPtr<FQueryTreeItem> Item = GroupedQueries[i];
if(Item->bIsGroup && ((InGroupName != NAME_None && InGroupName == Item->GroupName) || (InFrameNum != INDEX_NONE && InFrameNum == Item->FrameNum)))
{
return Item;
}
}
return NULL;
}
/** Functor for comparing query by CPU time */
struct FCompareQueryByCPUTime
{
FCollisionAnalyzer* Analyzer;
FCompareQueryByCPUTime(FCollisionAnalyzer* InAnalyzer)
: Analyzer(InAnalyzer)
{}
FORCEINLINE bool operator()( const TSharedPtr<FQueryTreeItem> A, const TSharedPtr<FQueryTreeItem> B ) const
{
check(A.IsValid());
check(!A->bIsGroup);
check(B.IsValid());
check(!A->bIsGroup);
const FCAQuery& QueryA = Analyzer->Queries[A->QueryIndex];
const FCAQuery& QueryB = Analyzer->Queries[B->QueryIndex];
return ( QueryA.CPUTime > QueryB.CPUTime );
}
};
/** Functor for comparing query by ID */
struct FCompareQueryByID
{
FCollisionAnalyzer* Analyzer;
EColumnSortMode::Type SortMode;
FCompareQueryByID(FCollisionAnalyzer* InAnalyzer, EColumnSortMode::Type InSortMode)
: Analyzer(InAnalyzer), SortMode(InSortMode)
{}
FORCEINLINE bool operator()(const TSharedPtr<FQueryTreeItem> A, const TSharedPtr<FQueryTreeItem> B) const
{
check(A.IsValid());
check(!A->bIsGroup);
check(B.IsValid());
check(!A->bIsGroup);
const FCAQuery& QueryA = Analyzer->Queries[A->QueryIndex];
const FCAQuery& QueryB = Analyzer->Queries[B->QueryIndex];
if (SortMode == EColumnSortMode::Descending)
{
return (QueryA.ID < QueryB.ID);
}
else
{
return (QueryA.ID > QueryB.ID);
}
}
};
/** Functor for comparing group by CPU time */
struct FCompareGroupByCPUTime
{
FORCEINLINE bool operator()( const TSharedPtr<FQueryTreeItem> A, const TSharedPtr<FQueryTreeItem> B ) const
{
check(A.IsValid());
check(A->bIsGroup);
check(B.IsValid());
check(B->bIsGroup);
return ( A->TotalCPUTime > B->TotalCPUTime );
}
};
void SCollisionAnalyzer::AddQueryToGroupedQueries(int32 NewQueryIndex, bool bPerformSort)
{
TSharedPtr<FQueryTreeItem> NewItem = FQueryTreeItem::MakeQuery(NewQueryIndex);
const FCAQuery& Query = Analyzer->Queries[NewQueryIndex];
// If not grouping, easy, just add to root list
if(GroupBy == EQueryGroupMode::Ungrouped)
{
GroupedQueries.Add(NewItem);
if(bPerformSort)
{
if(SortBy == EQuerySortMode::ByTime)
{
GroupedQueries.Sort( FCompareQueryByCPUTime(Analyzer) );
}
else if (SortBy == EQuerySortMode::ByID)
{
GroupedQueries.Sort( FCompareQueryByID(Analyzer, SortDirection) );
}
}
}
// If we are grouping..
else
{
// .. first find the existing group that we belong to
FName GroupName = NAME_None;
int32 FrameNum = INDEX_NONE;
if(GroupBy == EQueryGroupMode::ByTag || GroupBy == EQueryGroupMode::ByOwnerTag)
{
GroupName = (GroupBy == EQueryGroupMode::ByTag) ? Query.Params.TraceTag : Query.Params.OwnerTag;
}
else if(GroupBy == EQueryGroupMode::ByFrameNum)
{
FrameNum = Query.FrameNum;
}
TSharedPtr<FQueryTreeItem> AddToGroup = FindQueryGroup(GroupName, FrameNum);
// If we failed to find it - make group now
if(!AddToGroup.IsValid())
{
AddToGroup = FQueryTreeItem::MakeGroup(GroupName, FrameNum);
GroupedQueries.Add(AddToGroup);
}
// Finally, add item to that group
AddToGroup->QueriesInGroup.Add(NewItem);
AddToGroup->UpdateTotalCPUTime(Analyzer); // update total CPU time
if(bPerformSort)
{
if(SortBy == EQuerySortMode::ByTime)
{
GroupedQueries.Sort( FCompareGroupByCPUTime() );
AddToGroup->QueriesInGroup.Sort( FCompareQueryByCPUTime(Analyzer) );
}
else if (SortBy == EQuerySortMode::ByID)
{
GroupedQueries.Sort( FCompareGroupByCPUTime() );
AddToGroup->QueriesInGroup.Sort( FCompareQueryByID(Analyzer, SortDirection) );
}
}
}
// Update list of recent queries
RecentQueries.Add(NewQueryIndex);
if(RecentQueries.Num() > NumDrawRecentQueries)
{
RecentQueries.RemoveAt(0);
}
// Update total queries
TotalNumQueries++;
}
void SCollisionAnalyzer::RebuildFilteredList()
{
QueryDetailsWidget->ClearCurrentQuery();
GroupedQueries.Reset();
RecentQueries.Empty();
TotalNumQueries = 0.f;
// Run over results to find which ones pass (@TODO progress bar?)
for(int32 QueryIdx=0; QueryIdx < Analyzer->Queries.Num(); QueryIdx++)
{
const FCAQuery& Query = Analyzer->Queries[QueryIdx];
if(ShouldDisplayQuery(Query))
{
// Passed filter so add to filtered results (defer sorting until end)
AddQueryToGroupedQueries(QueryIdx, false);
}
}
// We have built all the lists, now sort (if desired)
if(SortBy == EQuerySortMode::ByTime)
{
// Ungrouped
if(GroupBy == EQueryGroupMode::Ungrouped)
{
GroupedQueries.Sort( FCompareQueryByCPUTime(Analyzer) );
}
// Grouped
else
{
GroupedQueries.Sort( FCompareGroupByCPUTime() );
for(int i=0; i<GroupedQueries.Num(); i++)
{
TSharedPtr<FQueryTreeItem> Group = GroupedQueries[i];
check(Group->bIsGroup);
Group->QueriesInGroup.Sort( FCompareQueryByCPUTime(Analyzer) );
}
}
}
else if (SortBy == EQuerySortMode::ByID)
{
// Ungrouped
if (GroupBy == EQueryGroupMode::Ungrouped)
{
GroupedQueries.Sort(FCompareQueryByID(Analyzer, SortDirection));
}
// Grouped
else
{
GroupedQueries.Sort(FCompareGroupByCPUTime()); // Sort groups by time again, doesn't make sense to sort by
for (int i = 0; i < GroupedQueries.Num(); i++)
{
TSharedPtr<FQueryTreeItem> Group = GroupedQueries[i];
check(Group->bIsGroup);
Group->QueriesInGroup.Sort(FCompareQueryByID(Analyzer, SortDirection));
}
}
}
// When underlying array changes, refresh list
QueryTreeWidget->RequestTreeRefresh();
}
//////////////////////////////////////////////////////////////////////////
const FSlateBrush* SCollisionAnalyzer::GetRecordButtonBrush() const
{
if(Analyzer->IsRecording())
{
// If recording, show stop button
return FCollisionAnalyzerStyle::Get()->GetBrush("CollisionAnalyzer.Stop");
}
else
{
// If stopped, show record button
return FCollisionAnalyzerStyle::Get()->GetBrush("CollisionAnalyzer.Record");
}
}
FText SCollisionAnalyzer::GetStatusText() const
{
return FText::Format(
LOCTEXT("CollisionAnalyzerStatusTextFmt", "Total: {0} queries over {1} frames. Shown: {2} queries"),
FText::AsNumber(Analyzer->Queries.Num()), FText::AsNumber(Analyzer->GetNumFramesOfRecording()), FText::AsNumber(TotalNumQueries)
);
}
ECheckBoxState SCollisionAnalyzer::GetDrawRecentState() const
{
return bDrawRecentQueries ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
FReply SCollisionAnalyzer::OnRecordButtonClicked()
{
// Toggle recording state
Analyzer->SetIsRecording(!Analyzer->IsRecording());
return FReply::Handled();
}
FReply SCollisionAnalyzer::OnLoadButtonClicked()
{
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
if (DesktopPlatform)
{
// Default path to find stats
const FString DefaultPath = FPaths::ProfilingDir() + TEXT("CollisionAnalyzer");
// File open dialog
TArray<FString> Filenames;
bool bOpened = DesktopPlatform->OpenFileDialog(
FSlateApplication::Get().FindBestParentWindowHandleForDialogs(AsShared()),
LOCTEXT("CollisionFileOpen", "Choose collision file to load").ToString(),
DefaultPath,
TEXT(""),
TEXT( "UCA file|*.uca" ),
EFileDialogFlags::None,
Filenames
);
// If we chose a file
if(bOpened && Filenames.Num() > 0)
{
Analyzer->LoadCollisionProfileData(Filenames[0]);
}
}
return FReply::Handled();
}
FReply SCollisionAnalyzer::OnSaveButtonClicked()
{
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
if (DesktopPlatform)
{
// Default path to find stats
const FString DefaultPath = FPaths::ProfilingDir() + TEXT("CollisionAnalyzer");
// File save dialog
TArray<FString> Filenames;
bool bSaved = DesktopPlatform->SaveFileDialog(
FSlateApplication::Get().FindBestParentWindowHandleForDialogs(AsShared()),
LOCTEXT("CollisionFileLocation", "Choose file location").ToString(),
DefaultPath,
TEXT(""),
TEXT( "UCA file|*.uca" ),
EFileDialogFlags::None,
Filenames
);
// If we chose a file
if(bSaved && Filenames.Num() > 0)
{
Analyzer->SaveCollisionProfileData(Filenames[0]);
}
}
return FReply::Handled();
}
void SCollisionAnalyzer::OnDrawRecentChanged(ECheckBoxState NewState)
{
bDrawRecentQueries = (NewState == ECheckBoxState::Checked);
}
// By frame
ECheckBoxState SCollisionAnalyzer::GetGroupByFrameState() const
{
return (GroupBy == EQueryGroupMode::ByFrameNum) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
void SCollisionAnalyzer::OnGroupByFrameChanged(ECheckBoxState NewState)
{
GroupBy = (NewState == ECheckBoxState::Checked) ? EQueryGroupMode::ByFrameNum : EQueryGroupMode::Ungrouped;
RebuildFilteredList();
}
// By Tag
ECheckBoxState SCollisionAnalyzer::GetGroupByTagState() const
{
return (GroupBy == EQueryGroupMode::ByTag) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
void SCollisionAnalyzer::OnGroupByTagChanged(ECheckBoxState NewState)
{
GroupBy = (NewState == ECheckBoxState::Checked) ? EQueryGroupMode::ByTag : EQueryGroupMode::Ungrouped;
RebuildFilteredList();
}
// By Owner
ECheckBoxState SCollisionAnalyzer::GetGroupByOwnerState() const
{
return (GroupBy == EQueryGroupMode::ByOwnerTag) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
void SCollisionAnalyzer::OnGroupByOwnerChanged(ECheckBoxState NewState)
{
GroupBy = (NewState == ECheckBoxState::Checked) ? EQueryGroupMode::ByOwnerTag : EQueryGroupMode::Ungrouped;
RebuildFilteredList();
}
void SCollisionAnalyzer::FilterTextCommitted(const FText& CommentText, ETextCommit::Type CommitInfo)
{
UpdateFilterInfo();
RebuildFilteredList();
}
void SCollisionAnalyzer::OnSortByChanged(const EColumnSortPriority::Type SortPriority, const FName& ColumnName, const EColumnSortMode::Type NewSortMode)
{
if(ColumnName == TimeColumnName)
{
// Only makes sense to sort by time decreasing
SortBy = EQuerySortMode::ByTime;
SortDirection = EColumnSortMode::Descending;
}
else if(ColumnName == IDColumnName)
{
// If already sorting by ID, flip direction
if (SortBy == EQuerySortMode::ByID)
{
SortDirection = (SortDirection == EColumnSortMode::Descending) ? EColumnSortMode::Ascending : EColumnSortMode::Descending;
}
// If not, sort by ID, and default to ascending
else
{
SortBy = EQuerySortMode::ByID;
SortDirection = EColumnSortMode::Descending;
}
}
RebuildFilteredList();
}
EColumnSortMode::Type SCollisionAnalyzer::GetIDSortMode() const
{
return (SortBy == EQuerySortMode::ByID) ? SortDirection : EColumnSortMode::None;
}
EColumnSortMode::Type SCollisionAnalyzer::GetTimeSortMode() const
{
return (SortBy == EQuerySortMode::ByTime) ? SortDirection : EColumnSortMode::None;
}
FString SCollisionAnalyzer::QueryTypeToString(ECAQueryType::Type QueryType)
{
switch (QueryType)
{
case ECAQueryType::Raycast:
return FString(TEXT("Raycast"));
case ECAQueryType::GeomSweep:
return FString(TEXT("Sweep"));
case ECAQueryType::GeomOverlap:
return FString(TEXT("Overlap"));
}
return FString(TEXT("UNKNOWNN"));
}
FString SCollisionAnalyzer::QueryShapeToString(ECAQueryShape::Type QueryShape)
{
switch(QueryShape)
{
case ECAQueryShape::Sphere:
return FString(TEXT("Sphere"));
case ECAQueryShape::Box:
return FString(TEXT("Box"));
case ECAQueryShape::Capsule:
return FString(TEXT("Capsule"));
case ECAQueryShape::Convex:
return FString(TEXT("Convex"));
}
return FString(TEXT("UNKNOWNN"));
}
FString SCollisionAnalyzer::QueryModeToString(ECAQueryMode::Type QueryMode)
{
switch(QueryMode)
{
case ECAQueryMode::Test:
return FString(TEXT("Test"));
case ECAQueryMode::Single:
return FString(TEXT("Single"));
case ECAQueryMode::Multi:
return FString(TEXT("Multi"));
}
return FString(TEXT("UNKNOWNN"));
}
#undef LOCTEXT_NAMESPACE