Files
UnrealEngine/Engine/Source/Editor/MassEntityDebugger/Private/SMassProcessorsWidget.cpp
2025-05-18 13:04:45 +08:00

320 lines
8.6 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SMassProcessorsWidget.h"
#include "SMassProcessor.h"
#include "Styling/AppStyle.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Text/STextBlock.h"
#include "SourceCodeNavigation.h"
#include "MassDebuggerStyle.h"
#define LOCTEXT_NAMESPACE "SMassDebugger"
SMassProcessorWidget::~SMassProcessorWidget()
{
if (DebuggerModel.IsValid())
{
DebuggerModel->OnFragmentSelectedDelegate.Remove(OnFragmentSelectChangeHandle);
}
}
void SMassProcessorWidget::Construct(const FArguments& InArgs, TSharedPtr<FMassDebuggerProcessorData> InDebuggerProcessorData, TSharedRef<FMassDebuggerModel> InDebuggerModel)
{
ProcessorData = InDebuggerProcessorData;
DebuggerModel = InDebuggerModel;
OnFragmentSelectChangeHandle = DebuggerModel->OnFragmentSelectedDelegate.AddSP(this, &SMassProcessorWidget::HandleFragmentSelected);
bIsExpandedText = false;
bIsExpandedGraph = false;
if (!ProcessorData.IsValid())
{
ChildSlot
[
SNew(STextBlock)
.TextStyle(FAppStyle::Get(), "LargeText")
.Text(LOCTEXT("InvalidDebugProcessorData", "Invalid Debug Processor Data"))
];
return;
}
FMassDebuggerProcessorData& ProcData = *ProcessorData.Get();
ChildSlot
[
SAssignNew(Border, SBorder)
.BorderImage(GetBorderByFragmentSelection())
.BorderBackgroundColor(FLinearColor::Gray)
.Padding(1.0f)
[
SNew(SBorder)
.BorderImage(FMassDebuggerStyle::GetBrush("MassDebug.Processor.InnerBackground"))
.Padding(2.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "NoBorder")
.Content()
[
SNew(STextBlock)
.TextStyle(FAppStyle::Get(), "NormalText")
.Text(FText::FromString(ProcData.Label))
]
.OnClicked(FOnClicked::CreateSP(this, &SMassProcessorWidget::HandleSelectProcessorClicked))
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "NoBorder")
.ToolTipText(LOCTEXT("ShowFragmentAccess", "Show Fragment Access"))
.Content()
[
SNew(SImage)
.Image(FAppStyle::Get().GetBrush("Icons.Layout"))
]
.OnClicked(FOnClicked::CreateSP(this, &SMassProcessorWidget::HandleExpandTextClicked))
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "NoBorder")
.ToolTipText(LOCTEXT("ShowStats", "Show Stats"))
.Content()
[
SNew(SImage)
.Image(FAppStyle::Get().GetBrush("Icons.LOD"))
]
.OnClicked(FOnClicked::CreateSP(this, &SMassProcessorWidget::HandleExpandGraphClicked))
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "NoBorder")
.ToolTipText(LOCTEXT("OpenSourceLocation", "Open Source Location"))
.Content()
[
SNew(SImage)
.Image(FAppStyle::Get().GetBrush("Icons.C++"))
]
.OnClicked(FOnClicked::CreateSP(this, &SMassProcessorWidget::HandleOpenSourceLocationClicked))
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "NoBorder")
.ToolTipText(LOCTEXT("ShowAffectedEntities", "Show Affected Entities"))
.Content()
[
SNew(SImage)
.Image(FAppStyle::Get().GetBrush("Icons.Search"))
]
.OnClicked(FOnClicked::CreateSP(this, &SMassProcessorWidget::HandleShowEntitiesClicked))
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f)
[
SAssignNew(TextBoxContainer, SBox)
.Visibility(bIsExpandedText ? EVisibility::Visible : EVisibility::Collapsed)
[
SNew(STextBlock)
.Text(LOCTEXT("DetailedProcessorInformation", "Detailed information about the processor..."))
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f)
[
SAssignNew(GraphBoxContainer, SBox)
.Visibility(bIsExpandedGraph ? EVisibility::Visible : EVisibility::Collapsed)
[
// This is a placeholder for the stats/perf graph widget
SNew(SImage)
.Image(FAppStyle::Get().GetBrush("Icons.LOD"))
]
]
]
]
];
}
FReply SMassProcessorWidget::HandleSelectProcessorClicked()
{
return FReply::Handled();
}
FReply SMassProcessorWidget::HandleExpandTextClicked()
{
bIsExpandedText = !bIsExpandedText;
if (TextBoxContainer.IsValid() && DebuggerModel.IsValid())
{
TextBoxContainer->SetContent(SNew(SMassProcessor, ProcessorData, DebuggerModel.ToSharedRef()));
TextBoxContainer->SetVisibility(bIsExpandedText ? EVisibility::Visible : EVisibility::Collapsed);
}
return FReply::Handled();
}
FReply SMassProcessorWidget::HandleExpandGraphClicked()
{
bIsExpandedGraph = !bIsExpandedGraph;
if (GraphBoxContainer.IsValid())
{
GraphBoxContainer->SetVisibility(bIsExpandedGraph ? EVisibility::Visible : EVisibility::Collapsed);
}
return FReply::Handled();
}
FReply SMassProcessorWidget::HandleOpenSourceLocationClicked()
{
if (!ProcessorData.IsValid())
{
return FReply::Handled();
}
const UMassProcessor* Proc = ProcessorData.Get()->Processor.Get();
if (Proc)
{
FSourceCodeNavigation::NavigateToClass(Proc->GetClass());
}
return FReply::Handled();
}
FReply SMassProcessorWidget::HandleShowEntitiesClicked()
{
#if WITH_MASSENTITY_DEBUG
if (!ProcessorData.IsValid() || !DebuggerModel.IsValid())
{
return FReply::Handled();
}
const UMassProcessor* Proc = ProcessorData.Get()->Processor.Get();
if (Proc)
{
TConstArrayView<FMassEntityQuery*> Queries = FMassDebugger::GetProcessorQueries(*Proc);
DebuggerModel->ShowEntitiesView(0, Queries);
}
#endif
return FReply::Handled();
}
void SMassProcessorWidget::HandleFragmentSelected(FName SelectedFragment)
{
Border->SetBorderImage(GetBorderByFragmentSelection());
}
const FSlateBrush* SMassProcessorWidget::GetBorderByFragmentSelection()
{
enum AccessLevel
{
None = 0,
Require,
Read,
Write,
Block,
MAX
};
const FSlateBrush* AccessColor[] = {
FMassDebuggerStyle::GetBrush("MassDebug.Processor"),
FMassDebuggerStyle::GetBrush("MassDebug.Processor.AccessRequired"),
FMassDebuggerStyle::GetBrush("MassDebug.Processor.AccessRead"),
FMassDebuggerStyle::GetBrush("MassDebug.Processor.AccessWrite"),
FMassDebuggerStyle::GetBrush("MassDebug.Processor.AccessBlock")
};
static_assert(UE_ARRAY_COUNT(AccessColor) == int32(AccessLevel::MAX), "Ensure AccessLevel enum is 1:1 with the brush array");
FName SelectedFragment;
if (DebuggerModel.IsValid())
{
SelectedFragment = DebuggerModel->GetSelectedFragment();
}
auto HasSelectedBit = [&SelectedFragment](const auto& Bitset) -> bool {
#if WITH_MASSENTITY_DEBUG
for (int32 Index = 0; Index < Bitset.StructTypesBitArray.Num(); ++Index)
{
if (Bitset.StructTypesBitArray[Index])
{
FName setName = Bitset.GetImplementation().GetStructTracker().DebugGetStructTypeName(Index);
if (setName == SelectedFragment)
{
return true;
}
}
}
#endif
return false;
};
auto GetHighestAccessLevel = [&SelectedFragment, &HasSelectedBit](const auto& ExecutionAccess, AccessLevel Highest) -> AccessLevel {
if (Highest < AccessLevel::Write && HasSelectedBit(ExecutionAccess.Write))
{
return AccessLevel::Write;
}
if (Highest < AccessLevel::Read && HasSelectedBit(ExecutionAccess.Read))
{
return AccessLevel::Read;
}
return Highest;
};
AccessLevel HighestAccess = AccessLevel::None;
if (!SelectedFragment.IsNone())
{
for (TSharedPtr<FMassDebuggerQueryData>& Query : ProcessorData->Queries)
{
FMassExecutionRequirements& ExecutionReqs = Query->ExecutionRequirements;
if (HighestAccess < AccessLevel::Block && HasSelectedBit(ExecutionReqs.RequiredNoneTags))
{
HighestAccess = AccessLevel::Block;
}
if (HighestAccess < AccessLevel::Require && (HasSelectedBit(ExecutionReqs.RequiredAnyTags) || HasSelectedBit(ExecutionReqs.RequiredAllTags)))
{
HighestAccess = AccessLevel::Require;
}
else
{
HighestAccess = GetHighestAccessLevel(ExecutionReqs.Fragments, HighestAccess);
HighestAccess = GetHighestAccessLevel(ExecutionReqs.ChunkFragments, HighestAccess);
HighestAccess = GetHighestAccessLevel(ExecutionReqs.SharedFragments, HighestAccess);
HighestAccess = GetHighestAccessLevel(ExecutionReqs.RequiredSubsystems, HighestAccess);
if (HighestAccess < AccessLevel::Read && HasSelectedBit(ExecutionReqs.ConstSharedFragments.Read))
{
HighestAccess = AccessLevel::Read;
}
}
}
}
return AccessColor[HighestAccess];
}
#undef LOCTEXT_NAMESPACE