You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
* moved UMassProcessor's inlines to the end of the header to keep the API look clean * moved the virtuals defined in the header over to the cpp * comments spellig fixes and improvements. #rb mieszko.zielinski [CL 36426024 by mieszko zielinski in 5.5 branch]
719 lines
23 KiB
C++
719 lines
23 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "MassDebuggerModel.h"
|
|
#include "MassProcessor.h"
|
|
#include "MassEntityManager.h"
|
|
#include "MassEntityQuery.h"
|
|
#include "Engine/Engine.h"
|
|
#include "MassDebugger.h"
|
|
#include "MassDebuggerSettings.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "Containers/UnrealString.h"
|
|
#include "MassArchetypeData.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "SMassDebugger"
|
|
|
|
namespace UE::Mass::Debugger::Private
|
|
{
|
|
template<typename TBitSet>
|
|
int32 BitSetDistance(const TBitSet& A, const TBitSet& B)
|
|
{
|
|
return (A - B).CountStoredTypes() + (B - A).CountStoredTypes();
|
|
}
|
|
|
|
float CalcArchetypeBitDistance(FMassDebuggerArchetypeData& A, FMassDebuggerArchetypeData& B)
|
|
{
|
|
int32 TotalLength = A.Composition.CountStoredTypes() + B.Composition.CountStoredTypes();
|
|
|
|
check(TotalLength > 0);
|
|
|
|
return float(BitSetDistance(A.Composition.Fragments, B.Composition.Fragments)
|
|
+ BitSetDistance(A.Composition.Tags, B.Composition.Tags)
|
|
+ BitSetDistance(A.Composition.ChunkFragments, B.Composition.ChunkFragments)
|
|
+ BitSetDistance(A.Composition.SharedFragments, B.Composition.SharedFragments))
|
|
/ TotalLength;
|
|
}
|
|
|
|
void MakeDisplayName(const FString& InName, FString& OutDisplayName)
|
|
{
|
|
OutDisplayName = InName;
|
|
if (GET_MASSDEBUGGER_CONFIG_VALUE(bStripMassPrefix) == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
OutDisplayName.RemoveFromStart(TEXT("Mass"), ESearchCase::CaseSensitive);
|
|
}
|
|
|
|
uint32 CalcProcessorHash(const UMassProcessor& Processor)
|
|
{
|
|
return PointerHash(&Processor);
|
|
}
|
|
|
|
/** We're ignoring all the CDO processors (since as such are not being run at runtime) as well ass processors owned
|
|
* by a CDO, for the very same reason. */
|
|
bool IsDebuggableProcessor(const UWorld* ContextWorld, const UMassProcessor& Processor)
|
|
{
|
|
return IsValid(&Processor)
|
|
&& Processor.HasAnyFlags(RF_ClassDefaultObject) == false
|
|
&& Processor.GetWorld() == ContextWorld
|
|
// checking ContextWorld is a cheaper way of supporting the declared behavior, since if there is a world then
|
|
// the processors are definitely not CDO owned (by design). Is there is no world we need to check specifically.
|
|
&& (ContextWorld != nullptr || Processor.GetOuter()->HasAnyFlags(RF_ClassDefaultObject) == false);
|
|
}
|
|
} // namespace UE::Mass::Debugger::Private
|
|
|
|
//----------------------------------------------------------------------//
|
|
// FMassDebuggerEnvironment
|
|
//----------------------------------------------------------------------//
|
|
FString FMassDebuggerEnvironment::GetDisplayName() const
|
|
{
|
|
FString DisplayName;
|
|
|
|
#if WITH_MASSENTITY_DEBUG
|
|
if (const FMassEntityManager* EntityManagerPtr = GetEntityManager())
|
|
{
|
|
DisplayName += EntityManagerPtr->DebugGetName();
|
|
if (DisplayName.Len())
|
|
{
|
|
DisplayName += TEXT(" - ");
|
|
}
|
|
}
|
|
#endif // WITH_MASSENTITY_DEBUG
|
|
|
|
const UWorld* WorldPtr = World.Get();
|
|
DisplayName += WorldPtr ? WorldPtr->GetPathName() : TEXT("No World");
|
|
return DisplayName;
|
|
}
|
|
|
|
const FMassEntityManager* FMassDebuggerEnvironment::GetEntityManager() const
|
|
{
|
|
return EntityManager.Pin().Get();
|
|
}
|
|
|
|
//----------------------------------------------------------------------//
|
|
// FMassDebuggerQueryData
|
|
//----------------------------------------------------------------------//
|
|
FMassDebuggerQueryData::FMassDebuggerQueryData(const FMassEntityQuery& Query, const FText& InLabel)
|
|
: Label(InLabel)
|
|
{
|
|
#if WITH_MASSENTITY_DEBUG
|
|
FMassDebugger::GetQueryExecutionRequirements(Query, ExecutionRequirements);
|
|
#endif // WITH_MASSENTITY_DEBUG
|
|
}
|
|
|
|
FMassDebuggerQueryData::FMassDebuggerQueryData(const FMassSubsystemRequirements& SubsystemRequirements, const FText& InLabel)
|
|
: Label(InLabel)
|
|
{
|
|
#if WITH_MASSENTITY_DEBUG
|
|
SubsystemRequirements.ExportRequirements(ExecutionRequirements);
|
|
#endif // WITH_MASSENTITY_DEBUG
|
|
}
|
|
|
|
int32 FMassDebuggerQueryData::GetTotalBitsUsedCount()
|
|
{
|
|
return ExecutionRequirements.GetTotalBitsUsedCount();
|
|
}
|
|
|
|
bool FMassDebuggerQueryData::IsEmpty() const
|
|
{
|
|
return ExecutionRequirements.IsEmpty();
|
|
}
|
|
|
|
//----------------------------------------------------------------------//
|
|
// FMassDebuggerProcessorData
|
|
//----------------------------------------------------------------------//
|
|
FMassDebuggerProcessorData::FMassDebuggerProcessorData(const UMassProcessor& InProcessor)
|
|
{
|
|
SetProcessor(InProcessor);
|
|
#if WITH_MASSENTITY_DEBUG
|
|
TConstArrayView<FMassEntityQuery*> ProcessorQueries = FMassDebugger::GetProcessorQueries(InProcessor);
|
|
|
|
ProcessorRequirements = MakeShareable(new FMassDebuggerQueryData(InProcessor.GetProcessorRequirements(), LOCTEXT("MassProcessorRequirementsLabel", "Processor Requirements")));
|
|
|
|
Queries.Reserve(ProcessorQueries.Num());
|
|
for (const FMassEntityQuery* Query : ProcessorQueries)
|
|
{
|
|
check(Query);
|
|
Queries.Add(MakeShareable(new FMassDebuggerQueryData(*Query, LOCTEXT("MassEntityQueryLabel", "Query"))));
|
|
}
|
|
#endif // WITH_MASSENTITY_DEBUG
|
|
}
|
|
|
|
FMassDebuggerProcessorData::FMassDebuggerProcessorData(const FMassEntityManager& EntityManager, UMassProcessor& InProcessor
|
|
, const TMap<FMassArchetypeHandle, TSharedPtr<FMassDebuggerArchetypeData>>& InTransientArchetypesMap)
|
|
{
|
|
SetProcessor(InProcessor);
|
|
#if WITH_MASSENTITY_DEBUG
|
|
TConstArrayView<FMassEntityQuery*> ProcessorQueries = FMassDebugger::GetUpToDateProcessorQueries(EntityManager, InProcessor);
|
|
|
|
ProcessorRequirements = MakeShareable(new FMassDebuggerQueryData(InProcessor.GetProcessorRequirements(), LOCTEXT("MassProcessorRequirementsLabel", "Processor Requirements")));
|
|
|
|
const FMassEntityHandle SelectedEntityHandle = UE::Mass::Debug::bTestSelectedEntityAgainstProcessorQueries
|
|
? FMassDebugger::GetSelectedEntity(EntityManager)
|
|
: FMassEntityHandle();
|
|
FStringOutputDevice SelectedEntityFailureJustificationLog;
|
|
SelectedEntityFailureJustificationLog.SetAutoEmitLineTerminator(true);
|
|
const FText SelectedEntityHandleDescription = UE::Mass::Debug::bTestSelectedEntityAgainstProcessorQueries
|
|
? FText::Format(LOCTEXT("WhyNotEntityJustificationLabel", "Why not entity {0}:"), FText::FromString(SelectedEntityHandle.DebugGetDescription()))
|
|
: FText();
|
|
|
|
Queries.Reserve(ProcessorQueries.Num());
|
|
for (const FMassEntityQuery* Query : ProcessorQueries)
|
|
{
|
|
check(Query);
|
|
TSharedPtr<FMassDebuggerQueryData>& QueryData = Queries.Add_GetRef(MakeShareable(new FMassDebuggerQueryData(*Query, LOCTEXT("MassEntityQueryLabel", "Query"))));
|
|
|
|
if (SelectedEntityHandle.IsValid())
|
|
{
|
|
const FMassArchetypeHandle ArchetypeHandle = EntityManager.GetArchetypeForEntity(SelectedEntityHandle);
|
|
if (ArchetypeHandle.IsValid() && Query->GetArchetypes().Contains(ArchetypeHandle) == false)
|
|
{
|
|
if (FMassArchetypeHelper::DoesArchetypeMatchRequirements(FMassArchetypeHelper::ArchetypeDataFromHandleChecked(ArchetypeHandle), *Query
|
|
, false, &SelectedEntityFailureJustificationLog) == false)
|
|
{
|
|
FTextBuilder DescriptionBuilder;
|
|
DescriptionBuilder.AppendLine(QueryData->AdditionalInformation);
|
|
DescriptionBuilder.AppendLine(SelectedEntityHandleDescription);
|
|
DescriptionBuilder.AppendLine(SelectedEntityFailureJustificationLog);
|
|
QueryData->AdditionalInformation = DescriptionBuilder.ToText();
|
|
|
|
SelectedEntityFailureJustificationLog.Reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const FMassArchetypeHandle& ArchetypeHandle : Query->GetArchetypes())
|
|
{
|
|
ValidArchetypes.Add(InTransientArchetypesMap.FindChecked(ArchetypeHandle));
|
|
}
|
|
}
|
|
#endif // WITH_MASSENTITY_DEBUG
|
|
}
|
|
|
|
void FMassDebuggerProcessorData::SetProcessor(const UMassProcessor& InProcessor)
|
|
{
|
|
Name = InProcessor.GetProcessorName();
|
|
UE::Mass::Debugger::Private::MakeDisplayName(Name, Label);
|
|
|
|
ProcessorHash = UE::Mass::Debugger::Private::CalcProcessorHash(InProcessor);
|
|
|
|
#if WITH_MASSENTITY_DEBUG
|
|
FStringOutputDevice DescriptionDevice;
|
|
InProcessor.DebugOutputDescription(DescriptionDevice);
|
|
if (DescriptionDevice != InProcessor.GetProcessorName())
|
|
{
|
|
Description = MoveTemp(DescriptionDevice);
|
|
}
|
|
#endif // WITH_MASSENTITY_DEBUG
|
|
}
|
|
|
|
//----------------------------------------------------------------------//
|
|
// FMassDebuggerArchetypeData
|
|
//----------------------------------------------------------------------//
|
|
FMassDebuggerArchetypeData::FMassDebuggerArchetypeData(const FMassArchetypeHandle& ArchetypeHandle)
|
|
{
|
|
#if WITH_MASSENTITY_DEBUG
|
|
Composition = FMassDebugger::GetArchetypeComposition(ArchetypeHandle);
|
|
|
|
// @todo should ensure we're using same hashing as the EntityManager here
|
|
CompositionHash = Composition.CalculateHash();
|
|
FullHash = CompositionHash;
|
|
|
|
FString FullHashAsString;
|
|
BytesToHexLower(reinterpret_cast<const uint8*>(&FullHash), sizeof(FullHash), FullHashAsString);
|
|
HashLabel = FText::FromString(FullHashAsString);
|
|
|
|
FMassDebugger::GetArchetypeEntityStats(ArchetypeHandle, ArchetypeStats);
|
|
|
|
const TConstArrayView<FName> DebugNames = FMassDebugger::GetArchetypeDebugNames(ArchetypeHandle);
|
|
|
|
if (DebugNames.IsEmpty())
|
|
{
|
|
// This archetype has no associated debug names, use hash as name.
|
|
FString HashAsString;
|
|
BytesToHexLower(reinterpret_cast<const uint8*>(&CompositionHash), sizeof(CompositionHash), HashAsString);
|
|
PrimaryDebugName = HashAsString;
|
|
|
|
// Use first fragment as name
|
|
if (FMassFragmentBitSet::FIndexIterator It = Composition.Fragments.GetIndexIterator())
|
|
{
|
|
const FName FirstStructName = Composition.Fragments.DebugGetStructTypeName(*It);
|
|
TStringBuilder<256> StringBuilder;
|
|
StringBuilder.Append(FirstStructName.ToString());
|
|
StringBuilder.Append(TEXT("..."));
|
|
Label = FText::FromString(StringBuilder.ToString());
|
|
}
|
|
else
|
|
{
|
|
Label = FText::FromString(HashAsString);
|
|
}
|
|
|
|
LabelLong = Label;
|
|
}
|
|
else
|
|
{
|
|
PrimaryDebugName = DebugNames[0].ToString();
|
|
|
|
TStringBuilder<256> StringBuilder;
|
|
|
|
// Short label for lists
|
|
StringBuilder.Reset();
|
|
StringBuilder.Append(DebugNames[0].ToString());
|
|
if (DebugNames.Num() > 1)
|
|
{
|
|
StringBuilder.Append(TEXT("..."));
|
|
}
|
|
Label = FText::FromString(StringBuilder.ToString());
|
|
|
|
// Longer label for info display
|
|
StringBuilder.Reset();
|
|
for (int i = 0; i < DebugNames.Num(); i++)
|
|
{
|
|
if (i > 0)
|
|
{
|
|
StringBuilder.Append(TEXT(", "));
|
|
}
|
|
StringBuilder.Append(DebugNames[i].ToString());
|
|
}
|
|
LabelLong = FText::FromString(StringBuilder.ToString());
|
|
|
|
// Label tooltip
|
|
StringBuilder.Reset();
|
|
for (int i = 0; i < DebugNames.Num(); i++)
|
|
{
|
|
if (i > 0)
|
|
{
|
|
StringBuilder.Append(TEXT("\n"));
|
|
}
|
|
StringBuilder.Append(DebugNames[i].ToString());
|
|
}
|
|
LabelTooltip = FText::FromString(StringBuilder.ToString());
|
|
}
|
|
|
|
#endif // WITH_MASSENTITY_DEBUG
|
|
}
|
|
|
|
int32 FMassDebuggerArchetypeData::GetTotalBitsUsedCount() const
|
|
{
|
|
return Composition.CountStoredTypes();
|
|
}
|
|
|
|
//----------------------------------------------------------------------//
|
|
// FMassDebuggerProcessingGraphNode
|
|
//----------------------------------------------------------------------//
|
|
FMassDebuggerProcessingGraphNode::FMassDebuggerProcessingGraphNode(const TSharedPtr<FMassDebuggerProcessorData>& InProcessorData, const UMassCompositeProcessor::FDependencyNode& InProcessorNode)
|
|
: ProcessorData(InProcessorData)
|
|
{
|
|
if (InProcessorNode.Processor == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
WaitForNodes = InProcessorNode.Dependencies;
|
|
}
|
|
|
|
FText FMassDebuggerProcessingGraphNode::GetLabel() const
|
|
{
|
|
if (ProcessorData.IsValid())
|
|
{
|
|
return FText::FromString(ProcessorData->Label);
|
|
}
|
|
|
|
return LOCTEXT("InvalidProcessor", "Invalid");
|
|
}
|
|
|
|
//----------------------------------------------------------------------//
|
|
// FMassDebuggerProcessingGraph
|
|
//----------------------------------------------------------------------//
|
|
FMassDebuggerProcessingGraph::FMassDebuggerProcessingGraph(const FMassDebuggerModel& DebuggerModel, UMassCompositeProcessor& InGraphOwner)
|
|
{
|
|
Label = InGraphOwner.GetProcessorName();
|
|
#if WITH_MASSENTITY_DEBUG
|
|
TConstArrayView<UMassCompositeProcessor::FDependencyNode> ProcessingGraph = FMassDebugger::GetProcessingGraph(InGraphOwner);
|
|
|
|
if (ProcessingGraph.Num() > 0)
|
|
{
|
|
GraphNodes.Reserve(ProcessingGraph.Num());
|
|
for (const UMassCompositeProcessor::FDependencyNode& Node : ProcessingGraph)
|
|
{
|
|
check(Node.Processor);
|
|
const TSharedPtr<FMassDebuggerProcessorData>& ProcessorData = DebuggerModel.GetProcessorDataChecked(*Node.Processor);
|
|
check(ProcessorData.IsValid());
|
|
GraphNodes.Add(FMassDebuggerProcessingGraphNode(ProcessorData, Node));
|
|
}
|
|
}
|
|
// it's possible for the graph to be empty if InGraphOwner has been populated for a single-thread execution.
|
|
// See if there are any processors owned by InGraphOwner.
|
|
else if (InGraphOwner.IsEmpty() == false)
|
|
{
|
|
TConstArrayView<TObjectPtr<UMassProcessor>> HostedProcessors = FMassDebugger::GetHostedProcessors(InGraphOwner);
|
|
for (const TObjectPtr<UMassProcessor>& Processor : HostedProcessors)
|
|
{
|
|
check(Processor);
|
|
const TSharedPtr<FMassDebuggerProcessorData>& ProcessorData = DebuggerModel.GetProcessorDataChecked(*Processor);
|
|
check(ProcessorData.IsValid());
|
|
GraphNodes.Add(FMassDebuggerProcessingGraphNode(ProcessorData));
|
|
}
|
|
|
|
// if we have processors, but the flat processing graph is empty, it means it's a single-threaded composite processor
|
|
bSingleTheadGraph = true;
|
|
}
|
|
#endif // WITH_MASSENTITY_DEBUG
|
|
}
|
|
|
|
//----------------------------------------------------------------------//
|
|
// FMassDebuggerModel
|
|
//----------------------------------------------------------------------//
|
|
FMassDebuggerModel::FMassDebuggerModel()
|
|
{
|
|
#if WITH_MASSENTITY_DEBUG
|
|
OnEntitySelectedHandle = FMassDebugger::OnEntitySelectedDelegate.AddRaw(this, &FMassDebuggerModel::OnEntitySelected);
|
|
#endif // WITH_MASSENTITY_DEBUG
|
|
}
|
|
|
|
FMassDebuggerModel::~FMassDebuggerModel()
|
|
{
|
|
#if WITH_MASSENTITY_DEBUG
|
|
if (OnEntitySelectedHandle.IsValid())
|
|
{
|
|
FMassDebugger::OnEntitySelectedDelegate.Remove(OnEntitySelectedHandle);
|
|
}
|
|
#endif // WITH_MASSENTITY_DEBUG
|
|
}
|
|
|
|
void FMassDebuggerModel::SetEnvironment(const TSharedPtr<FMassDebuggerEnvironment>& Item)
|
|
{
|
|
if (Item)
|
|
{
|
|
Environment = Item;
|
|
EnvironmentDisplayName = Item->GetDisplayName();
|
|
}
|
|
else
|
|
{
|
|
Environment = nullptr;
|
|
EnvironmentDisplayName.Reset();
|
|
}
|
|
|
|
RefreshAll();
|
|
}
|
|
|
|
void FMassDebuggerModel::RefreshAll()
|
|
{
|
|
TMap<FMassArchetypeHandle, TSharedPtr<FMassDebuggerArchetypeData>> TransientArchetypesMap;
|
|
|
|
CacheArchetypesData(TransientArchetypesMap);
|
|
CacheProcessorsData(TransientArchetypesMap);
|
|
CacheProcessingGraphs();
|
|
|
|
ClearArchetypeSelection();
|
|
|
|
OnRefreshDelegate.Broadcast();
|
|
}
|
|
|
|
void FMassDebuggerModel::SelectProcessor(TSharedPtr<FMassDebuggerProcessorData>& Processor)
|
|
{
|
|
SelectProcessors(MakeArrayView(&Processor, 1), ESelectInfo::Direct);
|
|
}
|
|
|
|
void FMassDebuggerModel::SelectProcessors(TArrayView<TSharedPtr<FMassDebuggerProcessorData>> Processors, ESelectInfo::Type SelectInfo)
|
|
{
|
|
SelectionMode = EMassDebuggerSelectionMode::Processor;
|
|
|
|
ResetSelectedProcessors();
|
|
ResetSelectedArchetypes();
|
|
|
|
SelectedProcessors = Processors;
|
|
|
|
for (TSharedPtr<FMassDebuggerProcessorData>& ProcessorData : SelectedProcessors)
|
|
{
|
|
check(ProcessorData.IsValid());
|
|
ProcessorData->Selection = EMassDebuggerProcessorSelection::Selected;
|
|
|
|
for (TSharedPtr<FMassDebuggerArchetypeData>& ArchetypeData : ProcessorData->ValidArchetypes)
|
|
{
|
|
SelectedArchetypes.AddUnique(ArchetypeData);
|
|
ArchetypeData->bIsSelected = true;
|
|
}
|
|
}
|
|
|
|
OnProcessorsSelectedDelegate.Broadcast(SelectedProcessors, SelectInfo);
|
|
}
|
|
|
|
void FMassDebuggerModel::ClearProcessorSelection()
|
|
{
|
|
SelectionMode = EMassDebuggerSelectionMode::None;
|
|
|
|
ResetSelectedProcessors();
|
|
|
|
OnProcessorsSelectedDelegate.Broadcast(SelectedProcessors, ESelectInfo::Direct);
|
|
}
|
|
|
|
void FMassDebuggerModel::SelectArchetypes(TArrayView<TSharedPtr<FMassDebuggerArchetypeData>> InSelectedArchetypes, ESelectInfo::Type SelectInfo)
|
|
{
|
|
ResetSelectedProcessors();
|
|
ResetSelectedArchetypes();
|
|
|
|
SelectionMode = EMassDebuggerSelectionMode::Archetype;
|
|
|
|
SelectedArchetypes = InSelectedArchetypes;
|
|
|
|
for (TSharedPtr<FMassDebuggerProcessorData>& ProcessorData : CachedProcessors)
|
|
{
|
|
check(ProcessorData.IsValid());
|
|
for (const TSharedPtr<FMassDebuggerArchetypeData>& ArchetypeData : InSelectedArchetypes)
|
|
{
|
|
if (ProcessorData->ValidArchetypes.Find(ArchetypeData) != INDEX_NONE)
|
|
{
|
|
ProcessorData->Selection = EMassDebuggerProcessorSelection::Selected;
|
|
SelectedProcessors.Add(ProcessorData);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
OnArchetypesSelectedDelegate.Broadcast(SelectedArchetypes, SelectInfo);
|
|
}
|
|
|
|
void FMassDebuggerModel::ClearArchetypeSelection()
|
|
{
|
|
SelectionMode = EMassDebuggerSelectionMode::None;
|
|
|
|
ResetSelectedArchetypes();
|
|
OnArchetypesSelectedDelegate.Broadcast(SelectedArchetypes, ESelectInfo::Direct);
|
|
}
|
|
|
|
void FMassDebuggerModel::CacheProcessorsData(const TMap<FMassArchetypeHandle, TSharedPtr<FMassDebuggerArchetypeData>>& InTransientArchetypesMap)
|
|
{
|
|
CachedProcessors.Reset();
|
|
|
|
UWorld* World = Environment ? Environment->World.Get() : nullptr;
|
|
const FMassEntityManager* EntityManager = Environment ? Environment->GetEntityManager() : nullptr;
|
|
|
|
if (EntityManager)
|
|
{
|
|
for (FThreadSafeObjectIterator It(UMassProcessor::StaticClass()); It; ++It)
|
|
{
|
|
UMassProcessor* Processor = Cast<UMassProcessor>(*It);
|
|
if (Processor
|
|
&& Cast<UMassCompositeProcessor>(Processor) == nullptr
|
|
&& UE::Mass::Debugger::Private::IsDebuggableProcessor(World, *Processor))
|
|
{
|
|
CachedProcessors.Add(MakeShareable(new FMassDebuggerProcessorData(*EntityManager, *Processor, InTransientArchetypesMap)));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (FThreadSafeObjectIterator It(UMassProcessor::StaticClass()); It; ++It)
|
|
{
|
|
UMassProcessor* Processor = Cast<UMassProcessor>(*It);
|
|
if (Processor
|
|
&& Cast<UMassCompositeProcessor>(Processor) == nullptr
|
|
&& UE::Mass::Debugger::Private::IsDebuggableProcessor(World, *Processor))
|
|
{
|
|
CachedProcessors.Add(MakeShareable(new FMassDebuggerProcessorData(*Processor)));
|
|
}
|
|
}
|
|
}
|
|
|
|
CachedProcessors.Sort([](const TSharedPtr<FMassDebuggerProcessorData>& A, const TSharedPtr<FMassDebuggerProcessorData>& B)
|
|
{
|
|
return A->Label < B->Label;
|
|
});
|
|
}
|
|
|
|
void FMassDebuggerModel::CacheProcessingGraphs()
|
|
{
|
|
CachedProcessingGraphs.Reset();
|
|
|
|
UWorld* World = Environment ? Environment->World.Get() : nullptr;
|
|
for (FThreadSafeObjectIterator It(UMassProcessor::StaticClass()); It; ++It)
|
|
{
|
|
UMassCompositeProcessor* Processor = Cast<UMassCompositeProcessor>(*It);
|
|
if (Processor && UE::Mass::Debugger::Private::IsDebuggableProcessor(World, *Processor))
|
|
{
|
|
CachedProcessingGraphs.Add(MakeShareable(new FMassDebuggerProcessingGraph(*this, *Processor)));
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMassDebuggerModel::CacheArchetypesData(TMap<FMassArchetypeHandle, TSharedPtr<FMassDebuggerArchetypeData>>& OutTransientArchetypesMap)
|
|
{
|
|
CachedAllArchetypes.Reset();
|
|
CachedArchetypeRepresentatives.Reset();
|
|
|
|
if (Environment)
|
|
{
|
|
if (const FMassEntityManager* EntityManager = Environment->GetEntityManager())
|
|
{
|
|
StoreArchetypes(*EntityManager, OutTransientArchetypesMap);
|
|
}
|
|
}
|
|
}
|
|
|
|
float FMassDebuggerModel::MinDistanceToSelectedArchetypes(const TSharedPtr<FMassDebuggerArchetypeData>& InArchetypeData) const
|
|
{
|
|
float MinDistance = MAX_flt;
|
|
for (const TSharedPtr<FMassDebuggerArchetypeData>& SelectedArchetype : SelectedArchetypes)
|
|
{
|
|
MinDistance = FMath::Min(MinDistance, ArchetypeDistances[SelectedArchetype->Index][InArchetypeData->Index]);
|
|
}
|
|
return MinDistance;
|
|
}
|
|
|
|
void FMassDebuggerModel::StoreArchetypes(const FMassEntityManager& EntityManager, TMap<FMassArchetypeHandle, TSharedPtr<FMassDebuggerArchetypeData>>& OutTransientArchetypesMap)
|
|
{
|
|
#if WITH_MASSENTITY_DEBUG
|
|
TArray<FMassArchetypeHandle> ArchetypeHandles = FMassDebugger::GetAllArchetypes(EntityManager);
|
|
|
|
CachedAllArchetypes.Reset(ArchetypeHandles.Num());
|
|
|
|
int32 MaxBitsUsed = 0;
|
|
|
|
// @todo build an archetype handle map
|
|
for (FMassArchetypeHandle& ArchetypeHandle : ArchetypeHandles)
|
|
{
|
|
FMassDebuggerArchetypeData* ArchetypeDataPtr = new FMassDebuggerArchetypeData(ArchetypeHandle);
|
|
ArchetypeDataPtr->Index = CachedAllArchetypes.Add(MakeShareable(ArchetypeDataPtr));
|
|
OutTransientArchetypesMap.Add(ArchetypeHandle, CachedAllArchetypes.Last());
|
|
|
|
MaxBitsUsed = FMath::Max(MaxBitsUsed, ArchetypeDataPtr->GetTotalBitsUsedCount());
|
|
}
|
|
#endif // WITH_MASSENTITY_DEBUG
|
|
|
|
// calculate distances
|
|
ArchetypeDistances.Reset();
|
|
ArchetypeDistances.AddDefaulted(CachedAllArchetypes.Num());
|
|
for (int i = 0; i < CachedAllArchetypes.Num(); ++i)
|
|
{
|
|
ArchetypeDistances[i].AddDefaulted(CachedAllArchetypes.Num());
|
|
}
|
|
|
|
for (int i = 0; i < CachedAllArchetypes.Num(); ++i)
|
|
{
|
|
for (int k = i + 1; k < CachedAllArchetypes.Num(); ++k)
|
|
{
|
|
const float Distance = UE::Mass::Debugger::Private::CalcArchetypeBitDistance(*CachedAllArchetypes[i].Get(), *CachedAllArchetypes[k].Get());
|
|
ArchetypeDistances[i][k] = Distance;
|
|
ArchetypeDistances[k][i] = Distance;
|
|
}
|
|
}
|
|
|
|
// Add archetypes that share same primary name under the same entry.
|
|
TMap<FString, TSharedPtr<FMassDebuggerArchetypeData>> ArchetypeNameMap;
|
|
for (TSharedPtr<FMassDebuggerArchetypeData>& ArchetypeData : CachedAllArchetypes)
|
|
{
|
|
if (const TSharedPtr<FMassDebuggerArchetypeData>* Representative = ArchetypeNameMap.Find(ArchetypeData->PrimaryDebugName))
|
|
{
|
|
(*Representative)->Children.Add(ArchetypeData);
|
|
ArchetypeData->Parent = *Representative;
|
|
}
|
|
else
|
|
{
|
|
ArchetypeNameMap.Add(ArchetypeData->PrimaryDebugName, ArchetypeData);
|
|
}
|
|
}
|
|
|
|
for (auto& KeyValue : ArchetypeNameMap)
|
|
{
|
|
CachedArchetypeRepresentatives.Add(KeyValue.Value);
|
|
}
|
|
}
|
|
|
|
FText FMassDebuggerModel::GetDisplayName() const
|
|
{
|
|
if (!Environment)
|
|
{
|
|
return LOCTEXT("PickEnvironment", "Pick Environment");
|
|
}
|
|
else if (IsStale())
|
|
{
|
|
return FText::FromString(FString::Printf(TEXT("(%s) %s")
|
|
, *(LOCTEXT("StaleEnvironmentPrefix", "Stale").ToString())
|
|
, *EnvironmentDisplayName));
|
|
}
|
|
|
|
return FText::FromString(Environment->GetDisplayName());
|
|
}
|
|
|
|
void FMassDebuggerModel::MarkAsStale()
|
|
{
|
|
if (Environment)
|
|
{
|
|
Environment->World = nullptr;
|
|
}
|
|
}
|
|
|
|
bool FMassDebuggerModel::IsStale() const
|
|
{
|
|
return Environment.IsValid() == false || (Environment->NeedsValidWorld() == true && Environment->IsWorldValid() == false);
|
|
}
|
|
|
|
const TSharedPtr<FMassDebuggerProcessorData>& FMassDebuggerModel::GetProcessorDataChecked(const UMassProcessor& Processor) const
|
|
{
|
|
check(CachedProcessors.Num());
|
|
|
|
const uint32 ProcessorHash = UE::Mass::Debugger::Private::CalcProcessorHash(Processor);
|
|
|
|
const TSharedPtr<FMassDebuggerProcessorData>* DataFound = CachedProcessors.FindByPredicate([ProcessorHash](const TSharedPtr<FMassDebuggerProcessorData>& Element)
|
|
{
|
|
return Element->ProcessorHash == ProcessorHash;
|
|
});
|
|
|
|
check(DataFound);
|
|
return *DataFound;
|
|
}
|
|
|
|
void FMassDebuggerModel::ResetSelectedArchetypes()
|
|
{
|
|
for (TSharedPtr<FMassDebuggerArchetypeData>& ArchetypeData : SelectedArchetypes)
|
|
{
|
|
ArchetypeData->bIsSelected = false;
|
|
}
|
|
SelectedArchetypes.Reset();
|
|
}
|
|
|
|
void FMassDebuggerModel::ResetSelectedProcessors()
|
|
{
|
|
// using CachedProcessors instead of SelectedProcessors to be on the safe side
|
|
for (TSharedPtr<FMassDebuggerProcessorData>& ProcessorData : CachedProcessors)
|
|
{
|
|
check(ProcessorData.IsValid());
|
|
ProcessorData->Selection = EMassDebuggerProcessorSelection::None;
|
|
}
|
|
SelectedProcessors.Reset();
|
|
}
|
|
|
|
void FMassDebuggerModel::OnEntitySelected(const FMassEntityManager& EntityManager, const FMassEntityHandle EntityHandle)
|
|
{
|
|
if (!Environment || Environment->GetEntityManager() != &EntityManager)
|
|
{
|
|
// not the entity manager we're debugging right now
|
|
return;
|
|
}
|
|
|
|
const FMassArchetypeHandle ArchetypeHandle = EntityManager.GetArchetypeForEntity(EntityHandle);
|
|
if (ArchetypeHandle.IsValid() == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
#if WITH_MASSENTITY_DEBUG
|
|
const uint32 ArchetypeHash = FMassDebugger::GetArchetypeComposition(ArchetypeHandle).CalculateHash();
|
|
#else
|
|
const uint32 ArchetypeHash = 0;
|
|
#endif // WITH_MASSENTITY_DEBUG
|
|
TSharedPtr<FMassDebuggerArchetypeData>* DebuggerArchetypeData = CachedAllArchetypes.FindByPredicate([ArchetypeHash](const TSharedPtr<FMassDebuggerArchetypeData>& Element)
|
|
{
|
|
return Element.IsValid() && Element->CompositionHash == ArchetypeHash;
|
|
});
|
|
|
|
if (DebuggerArchetypeData)
|
|
{
|
|
SelectArchetypes(MakeArrayView(DebuggerArchetypeData, 1), ESelectInfo::Direct);
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|