You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
- Added trace event for Phase (Push + Pop) to make it easier to recreate the event hierarchy when analyzing the traces. - Merged enums EStateTreeTraceInstanceEventType and EStateTreeTraceNodeEventType to EStateTreeTraceEventType which is mainly a list of verbs that could be reused for different events (States, Instances, Tasks, etc.) - EStateTreeUpdatePhase is no longer used as flags and reduced to uint8 - Phase events are stacked when producing traces and sent only if meaningful events (Task, State, Transition, etc.) are sent during their scope. This is to avoid sending useless events when the StateTree is ticked without any changes #rnx #rb mikko.mononen #preflight 6474a5e62e05bcc3309d093e [CL 25663847 by yoan stamant in ue5-main branch]
770 lines
23 KiB
C++
770 lines
23 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#if WITH_STATETREE_DEBUGGER
|
|
|
|
#include "Debugger/StateTreeDebugger.h"
|
|
#include "Algo/RemoveIf.h"
|
|
#include "Debugger/IStateTreeTraceProvider.h"
|
|
#include "Debugger/StateTreeTraceProvider.h"
|
|
#include "Debugger/StateTreeTraceTypes.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "StateTree.h"
|
|
#include "StateTreeModule.h"
|
|
#include "Trace/StoreClient.h"
|
|
#include "TraceServices/AnalysisService.h"
|
|
#include "TraceServices/ITraceServicesModule.h"
|
|
#include "TraceServices/Model/AnalysisSession.h"
|
|
#include "TraceServices/Model/Frames.h"
|
|
#include "Trace/Analyzer.h"
|
|
#include "Trace/Analysis.h"
|
|
#include "TraceServices/Model/Diagnostics.h"
|
|
#include "GenericPlatform/GenericPlatformMisc.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "StateTreeDebugger"
|
|
|
|
//----------------------------------------------------------------//
|
|
// UE::StateTreeDebugger
|
|
//----------------------------------------------------------------//
|
|
namespace UE::StateTreeDebugger
|
|
{
|
|
struct FDiagnosticsSessionAnalyzer : public UE::Trace::IAnalyzer
|
|
{
|
|
virtual void OnAnalysisBegin(const FOnAnalysisContext& Context) override
|
|
{
|
|
auto& Builder = Context.InterfaceBuilder;
|
|
Builder.RouteEvent(RouteId_Session2, "Diagnostics", "Session2");
|
|
}
|
|
|
|
virtual bool OnEvent(const uint16 RouteId, EStyle, const FOnEventContext& Context) override
|
|
{
|
|
const FEventData& EventData = Context.EventData;
|
|
|
|
switch (RouteId)
|
|
{
|
|
case RouteId_Session2:
|
|
{
|
|
EventData.GetString("Platform", SessionInfo.Platform);
|
|
EventData.GetString("AppName", SessionInfo.AppName);
|
|
EventData.GetString("CommandLine", SessionInfo.CommandLine);
|
|
EventData.GetString("Branch", SessionInfo.Branch);
|
|
EventData.GetString("BuildVersion", SessionInfo.BuildVersion);
|
|
SessionInfo.Changelist = EventData.GetValue<uint32>("Changelist", 0);
|
|
SessionInfo.ConfigurationType = (EBuildConfiguration) EventData.GetValue<uint8>("ConfigurationType");
|
|
SessionInfo.TargetType = (EBuildTargetType) EventData.GetValue<uint8>("TargetType");
|
|
|
|
return false;
|
|
}
|
|
default: ;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
enum : uint16
|
|
{
|
|
RouteId_Session2,
|
|
};
|
|
|
|
TraceServices::FSessionInfo SessionInfo;
|
|
};
|
|
|
|
} // UE::StateTreeDebugger
|
|
|
|
|
|
//----------------------------------------------------------------//
|
|
// FStateTreeDebugger
|
|
//----------------------------------------------------------------//
|
|
FStateTreeDebugger::FStateTreeDebugger()
|
|
: StateTreeModule(FModuleManager::GetModuleChecked<IStateTreeModule>("StateTreeModule"))
|
|
, ScrubState(EventCollections)
|
|
{
|
|
}
|
|
|
|
FStateTreeDebugger::~FStateTreeDebugger()
|
|
{
|
|
StopAnalysis();
|
|
}
|
|
|
|
void FStateTreeDebugger::Tick(const float DeltaTime)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FStateTreeDebugger::Tick);
|
|
|
|
if (RetryLoadLastLiveSessionTimer > 0.0f)
|
|
{
|
|
// We are still not connected to the last live session.
|
|
// Update polling timer and retry with remaining time; 0 or less will stop retries.
|
|
StartLastLiveSessionAnalysis(RetryLoadLastLiveSessionTimer - DeltaTime);
|
|
if (ActiveSessionTraceDescriptor.IsValid())
|
|
{
|
|
RetryLoadLastLiveSessionTimer = 0.0f;
|
|
}
|
|
}
|
|
|
|
UpdateInstances();
|
|
SyncToCurrentSessionDuration();
|
|
}
|
|
|
|
void FStateTreeDebugger::StopAnalysis()
|
|
{
|
|
if (const TraceServices::IAnalysisSession* Session = GetAnalysisSession())
|
|
{
|
|
Session->Stop(true);
|
|
AnalysisSession.Reset();
|
|
}
|
|
}
|
|
|
|
void FStateTreeDebugger::Pause()
|
|
{
|
|
bPaused = true;
|
|
}
|
|
|
|
void FStateTreeDebugger::Unpause()
|
|
{
|
|
// unpause and let next tick read the traces
|
|
bPaused = false;
|
|
}
|
|
|
|
void FStateTreeDebugger::SyncToCurrentSessionDuration()
|
|
{
|
|
if (const TraceServices::IAnalysisSession* Session = GetAnalysisSession())
|
|
{
|
|
{
|
|
TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session);
|
|
AnalysisDuration = Session->GetDurationSeconds();
|
|
}
|
|
ReadTrace(AnalysisDuration);
|
|
}
|
|
}
|
|
|
|
FText FStateTreeDebugger::GetInstanceDescription(FStateTreeInstanceDebugId InstanceId) const
|
|
{
|
|
using namespace UE::StateTreeDebugger;
|
|
const FInstanceDescriptor* FoundInstance = InstanceDescs.FindByPredicate([InstanceId](const FInstanceDescriptor& InstanceDesc)
|
|
{
|
|
return InstanceDesc.Id == InstanceId;
|
|
});
|
|
|
|
return (FoundInstance != nullptr) ? DescribeInstance(*FoundInstance) : LOCTEXT("InstanceNotFound","Instance not found");
|
|
}
|
|
|
|
void FStateTreeDebugger::SelectInstance(const FStateTreeInstanceDebugId InstanceId)
|
|
{
|
|
if (SelectedInstanceId != InstanceId)
|
|
{
|
|
SelectedInstanceId = InstanceId;
|
|
|
|
// Notify so listener can cleanup anything related to previous instance
|
|
OnSelectedInstanceCleared.ExecuteIfBound();
|
|
|
|
// Update event collection index for newly debugged instance
|
|
ScrubState.EventCollectionIndex = InstanceId.IsValid() ? EventCollections.IndexOfByPredicate([InstanceId = InstanceId](const UE::StateTreeDebugger::FInstanceEventCollection& Entry)
|
|
{
|
|
return Entry.InstanceId == InstanceId;
|
|
})
|
|
: INDEX_NONE;
|
|
|
|
// Set scrub time to refresh internal tracking for the new instance
|
|
// This will notify scrub position changed and active states
|
|
SetScrubTime(ScrubState.ScrubTime);
|
|
}
|
|
}
|
|
|
|
void FStateTreeDebugger::GetSessionInstances(TArray<UE::StateTreeDebugger::FInstanceDescriptor>& OutInstances) const
|
|
{
|
|
if (const TraceServices::IAnalysisSession* Session = GetAnalysisSession())
|
|
{
|
|
TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session);
|
|
|
|
if (const IStateTreeTraceProvider* Provider = Session->ReadProvider<IStateTreeTraceProvider>(FStateTreeTraceProvider::ProviderName))
|
|
{
|
|
Provider->GetInstances(OutInstances);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FStateTreeDebugger::UpdateInstances()
|
|
{
|
|
if (const TraceServices::IAnalysisSession* Session = GetAnalysisSession())
|
|
{
|
|
TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session);
|
|
|
|
if (const IStateTreeTraceProvider* Provider = Session->ReadProvider<IStateTreeTraceProvider>(FStateTreeTraceProvider::ProviderName))
|
|
{
|
|
Provider->GetInstances(InstanceDescs);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FStateTreeDebugger::StartLastLiveSessionAnalysis(const float RetryPollingDuration)
|
|
{
|
|
TArray<FTraceDescriptor> Traces;
|
|
GetLiveTraces(Traces);
|
|
|
|
if (Traces.Num())
|
|
{
|
|
StartSessionAnalysis(Traces.Last());
|
|
}
|
|
else
|
|
{
|
|
RetryLoadLastLiveSessionTimer = RetryPollingDuration;
|
|
ensure(RetryLoadLastLiveSessionTimer > 0);
|
|
UE_CLOG(RetryLoadLastLiveSessionTimer > 0, LogStateTree, Log, TEXT("Unable to start analysis for the most recent live session."));
|
|
}
|
|
}
|
|
|
|
void FStateTreeDebugger::StartSessionAnalysis(const FTraceDescriptor& TraceDescriptor)
|
|
{
|
|
if (ActiveSessionTraceDescriptor == TraceDescriptor)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UE::Trace::FStoreClient* StoreClient = GetStoreClient();
|
|
if (StoreClient == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Make sure any active analysis is stopped
|
|
StopAnalysis();
|
|
|
|
RecordingDuration = 0;
|
|
AnalysisDuration = 0;
|
|
LastTraceReadTime = 0;
|
|
|
|
const uint32 TraceId = TraceDescriptor.TraceId;
|
|
|
|
// Make sure it is still live
|
|
const UE::Trace::FStoreClient::FSessionInfo* SessionInfo = StoreClient->GetSessionInfoByTraceId(TraceId);
|
|
if (SessionInfo != nullptr)
|
|
{
|
|
UE::Trace::FStoreClient::FTraceData TraceData = StoreClient->ReadTrace(TraceId);
|
|
if (!TraceData)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FString TraceName(StoreClient->GetStatus()->GetStoreDir());
|
|
const UE::Trace::FStoreClient::FTraceInfo* TraceInfo = StoreClient->GetTraceInfoById(TraceId);
|
|
if (TraceInfo != nullptr)
|
|
{
|
|
FString Name(TraceInfo->GetName());
|
|
if (!Name.EndsWith(TEXT(".utrace")))
|
|
{
|
|
Name += TEXT(".utrace");
|
|
}
|
|
TraceName = FPaths::Combine(TraceName, Name);
|
|
FPaths::NormalizeFilename(TraceName);
|
|
}
|
|
|
|
ITraceServicesModule& TraceServicesModule = FModuleManager::LoadModuleChecked<ITraceServicesModule>("TraceServices");
|
|
if (const TSharedPtr<TraceServices::IAnalysisService> TraceAnalysisService = TraceServicesModule.GetAnalysisService())
|
|
{
|
|
checkf(!AnalysisSession.IsValid(), TEXT("Must make sure that current session was properly stopped before starting a new one otherwise it can cause threading issues"));
|
|
AnalysisSession = TraceAnalysisService->StartAnalysis(TraceId, *TraceName, MoveTemp(TraceData));
|
|
|
|
SyncToCurrentSessionDuration();
|
|
}
|
|
|
|
ActiveSessionTraceDescriptor = AnalysisSession.IsValid() ? TraceDescriptor : FTraceDescriptor();
|
|
}
|
|
}
|
|
|
|
void FStateTreeDebugger::GetLiveTraces(TArray<FTraceDescriptor>& OutTraceDescriptors) const
|
|
{
|
|
UE::Trace::FStoreClient* StoreClient = GetStoreClient();
|
|
if (StoreClient == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
OutTraceDescriptors.Reset();
|
|
|
|
const uint32 SessionCount = StoreClient->GetSessionCount();
|
|
for (uint32 SessionIndex = 0; SessionIndex < SessionCount; ++SessionIndex)
|
|
{
|
|
const UE::Trace::FStoreClient::FSessionInfo* SessionInfo = StoreClient->GetSessionInfo(SessionIndex);
|
|
if (SessionInfo != nullptr)
|
|
{
|
|
const uint32 TraceId = SessionInfo->GetTraceId();
|
|
const UE::Trace::FStoreClient::FTraceInfo* TraceInfo = StoreClient->GetTraceInfoById(TraceId);
|
|
if (TraceInfo != nullptr)
|
|
{
|
|
FTraceDescriptor& Trace = OutTraceDescriptors.AddDefaulted_GetRef();
|
|
Trace.TraceId = TraceId;
|
|
Trace.Name = FString(TraceInfo->GetName());
|
|
UpdateMetadata(Trace);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FStateTreeDebugger::UpdateMetadata(FTraceDescriptor& TraceDescriptor) const
|
|
{
|
|
UE::Trace::FStoreClient* StoreClient = GetStoreClient();
|
|
if (StoreClient == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const UE::Trace::FStoreClient::FTraceData TraceData = StoreClient->ReadTrace(TraceDescriptor.TraceId);
|
|
if (!TraceData)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// inspired from FStoreBrowser
|
|
struct FDataStream : public UE::Trace::IInDataStream
|
|
{
|
|
enum class EReadStatus
|
|
{
|
|
Ready = 0,
|
|
StoppedByReadSizeLimit
|
|
};
|
|
|
|
virtual int32 Read(void* Data, const uint32 Size) override
|
|
{
|
|
if (BytesRead >= 1024 * 1024)
|
|
{
|
|
Status = EReadStatus::StoppedByReadSizeLimit;
|
|
return 0;
|
|
}
|
|
const int32 InnerBytesRead = Inner->Read(Data, Size);
|
|
BytesRead += InnerBytesRead;
|
|
|
|
return InnerBytesRead;
|
|
}
|
|
|
|
virtual void Close() override
|
|
{
|
|
Inner->Close();
|
|
}
|
|
|
|
IInDataStream* Inner = nullptr;
|
|
int32 BytesRead = 0;
|
|
EReadStatus Status = EReadStatus::Ready;
|
|
};
|
|
|
|
FDataStream DataStream;
|
|
DataStream.Inner = TraceData.Get();
|
|
|
|
UE::StateTreeDebugger::FDiagnosticsSessionAnalyzer Analyzer;
|
|
UE::Trace::FAnalysisContext Context;
|
|
Context.AddAnalyzer(Analyzer);
|
|
Context.Process(DataStream).Wait();
|
|
|
|
TraceDescriptor.SessionInfo = Analyzer.SessionInfo;
|
|
}
|
|
|
|
FText FStateTreeDebugger::GetSelectedTraceDescription() const
|
|
{
|
|
if (ActiveSessionTraceDescriptor.IsValid())
|
|
{
|
|
return DescribeTrace(ActiveSessionTraceDescriptor);
|
|
}
|
|
|
|
return LOCTEXT("NoSelectedTraceDescriptor", "No trace selected");
|
|
}
|
|
|
|
void FStateTreeDebugger::SetScrubTime(const double ScrubTime)
|
|
{
|
|
ScrubState.SetScrubTime(ScrubTime);
|
|
OnScrubStateChanged.Execute(ScrubState);
|
|
|
|
RefreshActiveStates();
|
|
}
|
|
|
|
bool FStateTreeDebugger::IsActiveInstance(const double Time, const FStateTreeInstanceDebugId InstanceId) const
|
|
{
|
|
for (const UE::StateTreeDebugger::FInstanceDescriptor& Desc : InstanceDescs)
|
|
{
|
|
if (Desc.Id == InstanceId && Desc.Lifetime.Contains(Time))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FText FStateTreeDebugger::DescribeTrace(const FTraceDescriptor& TraceDescriptor)
|
|
{
|
|
if (TraceDescriptor.IsValid())
|
|
{
|
|
const TraceServices::FSessionInfo& SessionInfo = TraceDescriptor.SessionInfo;
|
|
|
|
return FText::FromString(FString::Printf(TEXT("%s-%s-%s-%s-%s"),
|
|
*LexToString(TraceDescriptor.TraceId),
|
|
*SessionInfo.Platform,
|
|
*SessionInfo.AppName,
|
|
LexToString(SessionInfo.ConfigurationType),
|
|
LexToString(SessionInfo.TargetType)));
|
|
}
|
|
|
|
return LOCTEXT("InvalidTraceDescriptor", "Invalid");
|
|
}
|
|
|
|
FText FStateTreeDebugger::DescribeInstance(const UE::StateTreeDebugger::FInstanceDescriptor& InstanceDesc)
|
|
{
|
|
if (InstanceDesc.IsValid() == false)
|
|
{
|
|
return LOCTEXT("NoSelectedInstanceDescriptor", "No instance selected");
|
|
}
|
|
return FText::FromString(LexToString(InstanceDesc));
|
|
}
|
|
|
|
void FStateTreeDebugger::SetActiveStates(const TConstArrayView<FStateTreeStateHandle> NewActiveStates)
|
|
{
|
|
ActiveStates = NewActiveStates;
|
|
OnActiveStatesChanged.ExecuteIfBound(ActiveStates);
|
|
}
|
|
|
|
void FStateTreeDebugger::RefreshActiveStates()
|
|
{
|
|
TArray<FStateTreeStateHandle> NewActiveStates;
|
|
|
|
if (ScrubState.IsPointingToValidActiveStates())
|
|
{
|
|
const UE::StateTreeDebugger::FInstanceEventCollection& EventCollection = EventCollections[ScrubState.EventCollectionIndex];
|
|
const int32 EventIndex = EventCollection.ActiveStatesChanges[ScrubState.ActiveStatesIndex].EventIndex;
|
|
NewActiveStates = EventCollection.Events[EventIndex].Get<FStateTreeTraceActiveStatesEvent>().ActiveStates;
|
|
}
|
|
|
|
SetActiveStates(NewActiveStates);
|
|
}
|
|
|
|
bool FStateTreeDebugger::CanStepBackToPreviousStateWithEvents() const
|
|
{
|
|
return bPaused ? ScrubState.HasPreviousFrame() : false;
|
|
}
|
|
|
|
void FStateTreeDebugger::StepBackToPreviousStateWithEvents()
|
|
{
|
|
ScrubState.GotoPreviousFrame();
|
|
OnScrubStateChanged.Execute(ScrubState);
|
|
|
|
RefreshActiveStates();
|
|
}
|
|
|
|
bool FStateTreeDebugger::CanStepForwardToNextStateWithEvents() const
|
|
{
|
|
return bPaused ? ScrubState.HasNextFrame() : false;
|
|
}
|
|
|
|
void FStateTreeDebugger::StepForwardToNextStateWithEvents()
|
|
{
|
|
ScrubState.GotoNextFrame();
|
|
OnScrubStateChanged.Execute(ScrubState);
|
|
|
|
RefreshActiveStates();
|
|
}
|
|
|
|
bool FStateTreeDebugger::CanStepBackToPreviousStateChange() const
|
|
{
|
|
return bPaused ? ScrubState.HasPreviousActiveStates() : false;
|
|
}
|
|
|
|
void FStateTreeDebugger::StepBackToPreviousStateChange()
|
|
{
|
|
ScrubState.GotoPreviousActiveStates();
|
|
OnScrubStateChanged.Execute(ScrubState);
|
|
|
|
RefreshActiveStates();
|
|
}
|
|
|
|
bool FStateTreeDebugger::CanStepForwardToNextStateChange() const
|
|
{
|
|
return bPaused ? ScrubState.HasNextActiveStates() : false;
|
|
}
|
|
|
|
void FStateTreeDebugger::StepForwardToNextStateChange()
|
|
{
|
|
ScrubState.GotoNextActiveStates();
|
|
OnScrubStateChanged.Execute(ScrubState);
|
|
|
|
RefreshActiveStates();
|
|
}
|
|
|
|
void FStateTreeDebugger::ToggleBreakpoints(const TConstArrayView<FStateTreeStateHandle> SelectedStates)
|
|
{
|
|
TArray<FStateTreeStateHandle> CurrentBreakpoints = StatesWithBreakpoint;
|
|
TArray<FStateTreeStateHandle> NewBreakpoints(SelectedStates);
|
|
|
|
// remove from the selected states any state that already has a breakpoint
|
|
const int32 ExistingBreakpointsStartIndex = Algo::RemoveIf
|
|
(NewBreakpoints,
|
|
[&CurrentBreakpoints](const FStateTreeStateHandle BreakpointToToggle)
|
|
{
|
|
return CurrentBreakpoints.Contains(BreakpointToToggle);
|
|
});
|
|
|
|
for (int32 i = ExistingBreakpointsStartIndex; i < NewBreakpoints.Num(); i++)
|
|
{
|
|
CurrentBreakpoints.RemoveSingleSwap(NewBreakpoints[i], /*bAllowShrinking*/false);
|
|
}
|
|
|
|
NewBreakpoints.SetNum(ExistingBreakpointsStartIndex, /*bAllowShrinking*/false);
|
|
|
|
// Both lists were reduced and can be merged as the new complete list of breakpoints.
|
|
CurrentBreakpoints.Append(NewBreakpoints);
|
|
|
|
StatesWithBreakpoint = CurrentBreakpoints;
|
|
|
|
OnBreakpointsChanged.ExecuteIfBound(StatesWithBreakpoint);
|
|
}
|
|
|
|
const TraceServices::IAnalysisSession* FStateTreeDebugger::GetAnalysisSession() const
|
|
{
|
|
return AnalysisSession.Get();
|
|
}
|
|
|
|
UE::Trace::FStoreClient* FStateTreeDebugger::GetStoreClient() const
|
|
{
|
|
return StateTreeModule.GetStoreClient();
|
|
}
|
|
|
|
void FStateTreeDebugger::ReadTrace(const uint64 FrameIndex)
|
|
{
|
|
if (const TraceServices::IAnalysisSession* Session = GetAnalysisSession())
|
|
{
|
|
TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session);
|
|
|
|
const TraceServices::IFrameProvider& FrameProvider = ReadFrameProvider(*Session);
|
|
|
|
if (const TraceServices::FFrame* TargetFrame = FrameProvider.GetFrame(TraceFrameType_Game, FrameIndex))
|
|
{
|
|
ReadTrace(*Session, FrameProvider, *TargetFrame);
|
|
}
|
|
}
|
|
|
|
// Notify outside session read scope
|
|
SendNotifications();
|
|
}
|
|
|
|
void FStateTreeDebugger::ReadTrace(const double ScrubTime)
|
|
{
|
|
if (const TraceServices::IAnalysisSession* Session = GetAnalysisSession())
|
|
{
|
|
TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session);
|
|
|
|
const TraceServices::IFrameProvider& FrameProvider = ReadFrameProvider(*Session);
|
|
|
|
TraceServices::FFrame TargetFrame;
|
|
if (FrameProvider.GetFrameFromTime(TraceFrameType_Game, ScrubTime, TargetFrame))
|
|
{
|
|
// Process only completed frames
|
|
if (TargetFrame.EndTime == std::numeric_limits<double>::infinity())
|
|
{
|
|
if (const TraceServices::FFrame* PreviousCompleteFrame = FrameProvider.GetFrame(TraceFrameType_Game, TargetFrame.Index - 1))
|
|
{
|
|
ReadTrace(*Session, FrameProvider, *PreviousCompleteFrame);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ReadTrace(*Session, FrameProvider, TargetFrame);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Notify outside session read scope
|
|
SendNotifications();
|
|
}
|
|
|
|
void FStateTreeDebugger::SendNotifications()
|
|
{
|
|
if (NewInstances.Num() > 0)
|
|
{
|
|
for (const FStateTreeInstanceDebugId NewInstanceId : NewInstances)
|
|
{
|
|
OnNewInstance.ExecuteIfBound(NewInstanceId);
|
|
}
|
|
NewInstances.Reset();
|
|
}
|
|
|
|
if (HitBreakpointStateIndex != INDEX_NONE)
|
|
{
|
|
check(HitBreakpointInstanceId.IsValid());
|
|
check(StatesWithBreakpoint.IsValidIndex(HitBreakpointStateIndex));
|
|
|
|
// Force scrub time to latest read time to reflect most recent events.
|
|
// This will notify scrub position changed and active states
|
|
SetScrubTime(LastTraceReadTime);
|
|
|
|
// Make sure the instance is selected in case the breakpoint was set for any instances
|
|
if (SelectedInstanceId != HitBreakpointInstanceId)
|
|
{
|
|
SelectInstance(HitBreakpointInstanceId);
|
|
}
|
|
|
|
OnBreakpointHit.ExecuteIfBound(HitBreakpointInstanceId, StatesWithBreakpoint[HitBreakpointStateIndex]);
|
|
|
|
HitBreakpointStateIndex = INDEX_NONE;
|
|
HitBreakpointInstanceId.Reset();
|
|
}
|
|
}
|
|
|
|
void FStateTreeDebugger::ReadTrace(
|
|
const TraceServices::IAnalysisSession& Session,
|
|
const TraceServices::IFrameProvider& FrameProvider,
|
|
const TraceServices::FFrame& Frame
|
|
)
|
|
{
|
|
TraceServices::FFrame LastReadFrame;
|
|
const bool bValidLastReadFrame = FrameProvider.GetFrameFromTime(TraceFrameType_Game, LastTraceReadTime, LastReadFrame);
|
|
if (LastTraceReadTime == 0 || (bValidLastReadFrame && Frame.Index > LastReadFrame.Index))
|
|
{
|
|
if (const IStateTreeTraceProvider* Provider = Session.ReadProvider<IStateTreeTraceProvider>(FStateTreeTraceProvider::ProviderName))
|
|
{
|
|
AddEvents(LastTraceReadTime, Frame.EndTime, FrameProvider, *Provider);
|
|
LastTraceReadTime = Frame.EndTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FStateTreeDebugger::ProcessEvent(const FStateTreeInstanceDebugId InstanceId, const TraceServices::FFrame& Frame, const FStateTreeTraceEventVariantType& Event)
|
|
{
|
|
using namespace UE::StateTreeDebugger;
|
|
|
|
// Breakpoints
|
|
if (bPaused == false // ignored when scrubbing a paused session
|
|
&& StateTreeAsset != nullptr // asset is required to properly match state handles
|
|
&& HitBreakpointStateIndex == INDEX_NONE // stop on first hit breakpoint
|
|
&& StatesWithBreakpoint.Num()
|
|
&& (SelectedInstanceId == InstanceId || SelectedInstanceId.IsInvalid())) // allow breakpoints on any instances if not specified
|
|
{
|
|
const FStateTreeTraceStateEvent* StateEvent = Event.TryGet<FStateTreeTraceStateEvent>();
|
|
if (StateEvent != nullptr && StateEvent->EventType == EStateTreeTraceEventType::OnEnter)
|
|
{
|
|
const UStateTree* InstanceStateTree = nullptr;
|
|
if (SelectedInstanceId.IsInvalid())
|
|
{
|
|
// No specific instance selected yet, find matching descriptor from id to extract the associated StateTree asset
|
|
const FInstanceDescriptor* FoundInstance = InstanceDescs.FindByPredicate(
|
|
[InstanceId](const FInstanceDescriptor& InstanceDesc)
|
|
{
|
|
return InstanceDesc.Id == InstanceId;
|
|
});
|
|
|
|
if (FoundInstance != nullptr)
|
|
{
|
|
InstanceStateTree = FoundInstance->StateTree.Get();
|
|
}
|
|
}
|
|
|
|
if (SelectedInstanceId.IsValid() || InstanceStateTree == StateTreeAsset)
|
|
{
|
|
HitBreakpointStateIndex = StatesWithBreakpoint.Find(StateEvent->GetStateHandle());
|
|
if (HitBreakpointStateIndex != INDEX_NONE)
|
|
{
|
|
HitBreakpointInstanceId = InstanceId;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FInstanceEventCollection* ExistingCollection = EventCollections.FindByPredicate([InstanceId](const FInstanceEventCollection& Entry)
|
|
{
|
|
return Entry.InstanceId == InstanceId;
|
|
});
|
|
|
|
// Create missing EventCollection if necessary
|
|
if (ExistingCollection == nullptr)
|
|
{
|
|
// Push deferred notification for new instance Id
|
|
NewInstances.Push(InstanceId);
|
|
|
|
// Update the active event collection index when it's newly created for the currently debugged instance.
|
|
// Otherwise (i.e. EventCollection already exists) it is updated when switching instance (i.e. SelectInstance)
|
|
if (SelectedInstanceId == InstanceId && ScrubState.EventCollectionIndex == INDEX_NONE)
|
|
{
|
|
ScrubState.EventCollectionIndex = EventCollections.Num();
|
|
}
|
|
|
|
ExistingCollection = &EventCollections.Emplace_GetRef(InstanceId);
|
|
}
|
|
|
|
check(ExistingCollection);
|
|
TArray<FStateTreeTraceEventVariantType>& Events = ExistingCollection->Events;
|
|
|
|
// Add new frame span if none added yet or new frame
|
|
if (ExistingCollection->FrameSpans.IsEmpty() || ExistingCollection->FrameSpans.Last().Frame.Index < Frame.Index)
|
|
{
|
|
double RecordingWorldTime = 0;
|
|
Visit([&RecordingWorldTime](auto& TypedEvent)
|
|
{
|
|
RecordingWorldTime = TypedEvent.RecordingWorldTime;
|
|
}, Event);
|
|
|
|
// Update global recording duration
|
|
RecordingDuration = RecordingWorldTime;
|
|
|
|
ExistingCollection->FrameSpans.Add(UE::StateTreeDebugger::FFrameSpan(Frame, RecordingWorldTime, Events.Num()));
|
|
}
|
|
|
|
// Add activate states change info
|
|
if (Event.IsType<FStateTreeTraceActiveStatesEvent>())
|
|
{
|
|
checkf(ExistingCollection->FrameSpans.Num() > 0, TEXT("Expecting to always be in a frame span at this point."));
|
|
const int32 FrameSpanIndex = ExistingCollection->FrameSpans.Num()-1;
|
|
|
|
// Add new entry for the first event or if the last event is for a different frame
|
|
if (ExistingCollection->ActiveStatesChanges.IsEmpty()
|
|
|| ExistingCollection->ActiveStatesChanges.Last().SpanIndex != FrameSpanIndex)
|
|
{
|
|
ExistingCollection->ActiveStatesChanges.Push({FrameSpanIndex, Events.Num()});
|
|
}
|
|
else
|
|
{
|
|
// Multiple events for change of active states in the same frame, keep the last one until we implement scrubbing within a frame
|
|
ExistingCollection->ActiveStatesChanges.Last().EventIndex = Events.Num();
|
|
}
|
|
}
|
|
|
|
// Store event in the collection
|
|
Events.Emplace(Event);
|
|
|
|
return /*bKeepProcessing*/true;
|
|
}
|
|
|
|
const UE::StateTreeDebugger::FInstanceEventCollection& FStateTreeDebugger::GetEventCollection(FStateTreeInstanceDebugId InstanceId) const\
|
|
{
|
|
using namespace UE::StateTreeDebugger;
|
|
const FInstanceEventCollection* ExistingCollection = EventCollections.FindByPredicate([InstanceId](const FInstanceEventCollection& Entry)
|
|
{
|
|
return Entry.InstanceId == InstanceId;
|
|
});
|
|
|
|
return ExistingCollection != nullptr ? *ExistingCollection : FInstanceEventCollection::Invalid;
|
|
}
|
|
|
|
void FStateTreeDebugger::AddEvents(const double StartTime, const double EndTime, const TraceServices::IFrameProvider& FrameProvider, const IStateTreeTraceProvider& StateTreeTraceProvider)
|
|
{
|
|
check(StateTreeAsset.IsValid());
|
|
StateTreeTraceProvider.ReadTimelines(*StateTreeAsset,
|
|
[this, StartTime, EndTime, &FrameProvider](const FStateTreeInstanceDebugId InstanceId, const IStateTreeTraceProvider::FEventsTimeline& TimelineData)
|
|
{
|
|
// Keep track of the frames containing events. Starting with an invalid frame.
|
|
TraceServices::FFrame Frame;
|
|
Frame.Index = INDEX_NONE;
|
|
|
|
TimelineData.EnumerateEvents(StartTime, EndTime,
|
|
[this, InstanceId, &FrameProvider, &Frame](const double EventStartTime, const double EventEndTime, uint32 InDepth, const FStateTreeTraceEventVariantType& Event)
|
|
{
|
|
// Fetch frame when not set yet or if events no longer part of the current one
|
|
if (Frame.Index == INDEX_NONE ||
|
|
(EventEndTime < Frame.StartTime || Frame.EndTime < EventStartTime))
|
|
{
|
|
FrameProvider.GetFrameFromTime(TraceFrameType_Game, EventStartTime, Frame);
|
|
}
|
|
|
|
const bool bKeepProcessing = ProcessEvent(InstanceId, Frame, Event);
|
|
return bKeepProcessing ? TraceServices::EEventEnumerate::Continue : TraceServices::EEventEnumerate::Stop;
|
|
});
|
|
});
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|
|
|
|
#endif // WITH_STATETREE_DEBUGGER
|