2023-03-14 13:35:46 -04:00
// 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"
2023-05-09 12:57:56 -04:00
# define LOCTEXT_NAMESPACE "StateTreeDebugger"
2023-03-14 13:35:46 -04:00
//----------------------------------------------------------------//
// 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 " ) )
2023-05-09 12:57:56 -04:00
, ScrubState ( EventCollections )
2023-03-14 13:35:46 -04:00
{
}
FStateTreeDebugger : : ~ FStateTreeDebugger ( )
{
StopAnalysis ( ) ;
}
void FStateTreeDebugger : : Tick ( float DeltaTime )
{
2023-05-09 12:57:56 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( FStateTreeDebugger : : Tick ) ;
2023-03-17 09:41:13 -04:00
2023-05-09 12:57:56 -04:00
UpdateInstances ( ) ;
SyncToCurrentSessionDuration ( ) ;
2023-03-14 13:35:46 -04:00
}
bool FStateTreeDebugger : : IsTickable ( ) const
{
return AnalysisSession . IsValid ( ) ;
}
void FStateTreeDebugger : : StopAnalysis ( )
{
if ( const TraceServices : : IAnalysisSession * Session = GetAnalysisSession ( ) )
{
Session - > Stop ( true ) ;
AnalysisSession . Reset ( ) ;
}
2023-05-09 12:57:56 -04:00
EventCollections . Reset ( ) ;
2023-03-14 13:35:46 -04:00
}
void FStateTreeDebugger : : Pause ( )
{
bPaused = true ;
// Force a refresh to make sure most recent traces are processed
SyncToCurrentSessionDuration ( ) ;
}
void FStateTreeDebugger : : Unpause ( )
{
// unpause and let next tick read the traces
bPaused = false ;
}
void FStateTreeDebugger : : SyncToCurrentSessionDuration ( )
{
if ( const TraceServices : : IAnalysisSession * Session = GetAnalysisSession ( ) )
{
2023-03-17 09:41:13 -04:00
{
TraceServices : : FAnalysisSessionReadScope SessionReadScope ( * Session ) ;
2023-05-09 12:57:56 -04:00
RecordingDuration = Session - > GetDurationSeconds ( ) ;
2023-03-17 09:41:13 -04:00
}
2023-05-09 12:57:56 -04:00
ReadTrace ( RecordingDuration ) ;
2023-03-17 09:41:13 -04:00
}
2023-03-14 13:35:46 -04:00
}
2023-05-09 12:57:56 -04:00
FText FStateTreeDebugger : : GetInstanceDescription ( FStateTreeInstanceDebugId InstanceId ) const
2023-03-14 13:35:46 -04:00
{
2023-05-09 12:57:56 -04:00
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 " ) ;
2023-03-14 13:35:46 -04:00
}
2023-05-09 12:57:56 -04:00
void FStateTreeDebugger : : SelectInstance ( const FStateTreeInstanceDebugId InstanceId )
2023-03-14 13:35:46 -04:00
{
2023-05-09 12:57:56 -04:00
if ( SelectedInstanceId ! = InstanceId )
2023-03-14 13:35:46 -04:00
{
2023-05-09 12:57:56 -04:00
HitBreakpointStateIndex = INDEX_NONE ;
HitBreakpointInstanceId . Reset ( ) ;
2023-03-14 13:35:46 -04:00
2023-05-09 12:57:56 -04:00
SelectedInstanceId = InstanceId ;
2023-03-14 13:35:46 -04:00
2023-05-09 12:57:56 -04:00
// Notify so listener can cleanup anything related to previous instance
OnSelectedInstanceCleared . ExecuteIfBound ( ) ;
2023-04-19 13:26:23 -04:00
2023-05-09 12:57:56 -04:00
// 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 ) ;
2023-03-14 13:35:46 -04:00
}
}
2023-05-09 12:57:56 -04:00
void FStateTreeDebugger : : GetSessionInstances ( TArray < UE : : StateTreeDebugger : : FInstanceDescriptor > & OutInstances ) const
2023-03-14 13:35:46 -04:00
{
if ( const TraceServices : : IAnalysisSession * Session = GetAnalysisSession ( ) )
{
TraceServices : : FAnalysisSessionReadScope SessionReadScope ( * Session ) ;
if ( const IStateTreeTraceProvider * Provider = Session - > ReadProvider < IStateTreeTraceProvider > ( FStateTreeTraceProvider : : ProviderName ) )
{
2023-05-09 12:57:56 -04:00
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 ) ;
2023-03-14 13:35:46 -04:00
}
}
}
void FStateTreeDebugger : : StartLastLiveSessionAnalysis ( )
{
TArray < FTraceDescriptor > Traces ;
GetLiveTraces ( Traces ) ;
if ( Traces . Num ( ) )
{
StartSessionAnalysis ( Traces . Last ( ) ) ;
}
}
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 ( ) ;
const uint32 TraceId = TraceDescriptor . TraceId ;
2023-05-09 12:57:56 -04:00
2023-03-14 13:35:46 -04:00
// 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 ;
}
2023-05-09 12:57:56 -04:00
FText FStateTreeDebugger : : GetSelectedTraceDescription ( ) const
2023-03-14 13:35:46 -04:00
{
if ( ActiveSessionTraceDescriptor . IsValid ( ) )
{
return DescribeTrace ( ActiveSessionTraceDescriptor ) ;
}
2023-05-09 12:57:56 -04:00
return LOCTEXT ( " NoSelectedTraceDescriptor " , " No trace selected " ) ;
2023-03-14 13:35:46 -04:00
}
2023-05-09 12:57:56 -04:00
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 )
2023-03-14 13:35:46 -04:00
{
if ( TraceDescriptor . IsValid ( ) )
{
const TraceServices : : FSessionInfo & SessionInfo = TraceDescriptor . SessionInfo ;
2023-05-09 12:57:56 -04:00
return FText : : FromString ( FString : : Printf ( TEXT ( " %s-%s-%s-%s-%s " ) ,
2023-03-14 13:35:46 -04:00
* LexToString ( TraceDescriptor . TraceId ) ,
* SessionInfo . Platform ,
* SessionInfo . AppName ,
LexToString ( SessionInfo . ConfigurationType ) ,
2023-05-09 12:57:56 -04:00
LexToString ( SessionInfo . TargetType ) ) ) ;
2023-03-14 13:35:46 -04:00
}
2023-05-09 12:57:56 -04:00
return LOCTEXT ( " InvalidTraceDescriptor " , " Invalid " ) ;
2023-03-14 13:35:46 -04:00
}
2023-05-09 12:57:56 -04:00
FText FStateTreeDebugger : : DescribeInstance ( const UE : : StateTreeDebugger : : FInstanceDescriptor & InstanceDesc )
2023-03-14 13:35:46 -04:00
{
2023-05-09 12:57:56 -04:00
if ( InstanceDesc . IsValid ( ) = = false )
2023-03-14 13:35:46 -04:00
{
2023-05-09 12:57:56 -04:00
return LOCTEXT ( " NoSelectedInstanceDescriptor " , " No instance selected " ) ;
2023-03-14 13:35:46 -04:00
}
2023-05-09 12:57:56 -04:00
return FText : : FromString ( LexToString ( InstanceDesc ) ) ;
2023-03-14 13:35:46 -04:00
}
void FStateTreeDebugger : : SetActiveStates ( const TConstArrayView < FStateTreeStateHandle > NewActiveStates )
{
ActiveStates = NewActiveStates ;
OnActiveStatesChanged . ExecuteIfBound ( ActiveStates ) ;
}
2023-05-09 12:57:56 -04:00
void FStateTreeDebugger : : RefreshActiveStates ( )
2023-03-14 13:35:46 -04:00
{
2023-05-09 12:57:56 -04:00
TArray < FStateTreeStateHandle > NewActiveStates ;
2023-03-14 13:35:46 -04:00
2023-05-09 12:57:56 -04:00
if ( ScrubState . IsPointingToValidActiveStates ( ) )
2023-03-14 13:35:46 -04:00
{
2023-05-09 12:57:56 -04:00
const UE : : StateTreeDebugger : : FInstanceEventCollection & EventCollection = EventCollections [ ScrubState . EventCollectionIndex ] ;
const int32 EventIndex = EventCollection . ActiveStatesChanges [ ScrubState . ActiveStatesIndex ] . Value ;
NewActiveStates = EventCollection . Events [ EventIndex ] . Get < FStateTreeTraceActiveStatesEvent > ( ) . ActiveStates ;
2023-03-14 13:35:46 -04:00
}
2023-05-09 12:57:56 -04:00
SetActiveStates ( NewActiveStates ) ;
2023-03-14 13:35:46 -04:00
}
2023-05-09 12:57:56 -04:00
bool FStateTreeDebugger : : CanStepBackToPreviousStateWithEvents ( ) const
2023-03-14 13:35:46 -04:00
{
2023-05-09 12:57:56 -04:00
return bPaused ? ScrubState . HasPreviousFrame ( ) : false ;
2023-03-14 13:35:46 -04:00
}
2023-05-09 12:57:56 -04:00
void FStateTreeDebugger : : StepBackToPreviousStateWithEvents ( )
2023-03-14 13:35:46 -04:00
{
2023-05-09 12:57:56 -04:00
ScrubState . GotoPreviousFrame ( ) ;
OnScrubStateChanged . Execute ( ScrubState ) ;
2023-03-14 13:35:46 -04:00
2023-05-09 12:57:56 -04:00
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 ( ) ;
2023-03-14 13:35:46 -04:00
}
void FStateTreeDebugger : : ToggleBreakpoints ( const TConstArrayView < FStateTreeStateHandle > SelectedStates )
{
2023-05-09 12:57:56 -04:00
TArray < FStateTreeStateHandle > CurrentBreakpoints = StatesWithBreakpoint ;
2023-03-14 13:35:46 -04:00
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 ) ;
}
}
2023-03-17 09:41:13 -04:00
// Notify outside session read scope
SendNotifications ( ) ;
2023-03-14 13:35:46 -04:00
}
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 ) )
{
2023-05-09 12:57:56 -04:00
// 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 ) ;
}
2023-03-14 13:35:46 -04:00
}
}
2023-03-17 09:41:13 -04:00
// Notify outside session read scope
SendNotifications ( ) ;
}
void FStateTreeDebugger : : SendNotifications ( )
{
2023-05-09 12:57:56 -04:00
if ( NewInstances . Num ( ) > 0 )
2023-03-17 09:41:13 -04:00
{
2023-05-09 12:57:56 -04:00
for ( const FStateTreeInstanceDebugId NewInstanceId : NewInstances )
{
OnNewInstance . ExecuteIfBound ( NewInstanceId ) ;
}
NewInstances . Reset ( ) ;
2023-03-17 09:41:13 -04:00
}
if ( HitBreakpointStateIndex ! = INDEX_NONE )
{
check ( HitBreakpointInstanceId . IsValid ( ) ) ;
check ( StatesWithBreakpoint . IsValidIndex ( HitBreakpointStateIndex ) ) ;
2023-05-09 12:57:56 -04:00
// Make sure the instance is selected in case the breakpoint was set for any instances
if ( SelectedInstanceId ! = HitBreakpointInstanceId )
{
SelectInstance ( HitBreakpointInstanceId ) ;
}
2023-03-17 09:41:13 -04:00
OnBreakpointHit . ExecuteIfBound ( HitBreakpointInstanceId , StatesWithBreakpoint [ HitBreakpointStateIndex ] ) ;
HitBreakpointStateIndex = INDEX_NONE ;
HitBreakpointInstanceId . Reset ( ) ;
}
2023-03-14 13:35:46 -04:00
}
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 = = UnsetTime | | ( bValidLastReadFrame & & Frame . Index > LastReadFrame . Index ) )
{
if ( const IStateTreeTraceProvider * Provider = Session . ReadProvider < IStateTreeTraceProvider > ( FStateTreeTraceProvider : : ProviderName ) )
{
AddEvents ( LastTraceReadTime , Frame . EndTime , FrameProvider , * Provider ) ;
2023-05-09 12:57:56 -04:00
LastTraceReadTime = Frame . EndTime ;
2023-03-14 13:35:46 -04:00
}
}
}
bool FStateTreeDebugger : : ProcessEvent ( const FStateTreeInstanceDebugId InstanceId , const TraceServices : : FFrame & Frame , const FStateTreeTraceEventVariantType & Event )
{
2023-05-09 12:57:56 -04:00
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
2023-03-14 13:35:46 -04:00
{
2023-05-09 12:57:56 -04:00
const FStateTreeTraceStateEvent * StateEvent = Event . TryGet < FStateTreeTraceStateEvent > ( ) ;
if ( StateEvent ! = nullptr & & StateEvent - > EventType = = EStateTreeTraceNodeEventType : : OnEnter )
2023-03-14 13:35:46 -04:00
{
2023-05-09 12:57:56 -04:00
const UStateTree * InstanceStateTree = nullptr ;
if ( SelectedInstanceId . IsInvalid ( ) )
2023-03-14 13:35:46 -04:00
{
2023-05-09 12:57:56 -04:00
// 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 )
2023-03-17 09:41:13 -04:00
{
2023-05-09 12:57:56 -04:00
return InstanceDesc . Id = = InstanceId ;
} ) ;
if ( FoundInstance ! = nullptr )
{
InstanceStateTree = FoundInstance - > StateTree . Get ( ) ;
2023-03-17 09:41:13 -04:00
}
2023-05-09 12:57:56 -04:00
}
2023-03-17 09:41:13 -04:00
2023-05-09 12:57:56 -04:00
if ( SelectedInstanceId . IsValid ( ) | | InstanceStateTree = = StateTreeAsset )
{
HitBreakpointStateIndex = StatesWithBreakpoint . Find ( FStateTreeStateHandle ( StateEvent - > Idx ) ) ;
if ( HitBreakpointStateIndex ! = INDEX_NONE )
2023-03-17 09:41:13 -04:00
{
2023-05-09 12:57:56 -04:00
HitBreakpointInstanceId = InstanceId ;
2023-03-14 13:35:46 -04:00
}
}
}
2023-05-09 12:57:56 -04:00
}
2023-03-14 13:35:46 -04:00
2023-05-09 12:57:56 -04:00
FInstanceEventCollection * ExistingCollection = EventCollections . FindByPredicate ( [ InstanceId ] ( const FInstanceEventCollection & Entry )
2023-03-14 13:35:46 -04:00
{
2023-05-09 12:57:56 -04:00
return Entry . InstanceId = = InstanceId ;
} ) ;
2023-03-17 09:41:13 -04:00
2023-05-09 12:57:56 -04:00
// 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 )
{
ExistingCollection - > FrameSpans . Add ( UE : : StateTreeDebugger : : FFrameSpan ( Frame , 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 uint32 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 ( ) . Key ! = 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 ( ) . Value = Events . Num ( ) ;
2023-03-14 13:35:46 -04:00
}
}
2023-05-09 12:57:56 -04:00
// Store event in the collection
Events . Emplace ( Event ) ;
2023-03-14 13:35:46 -04:00
return /*bKeepProcessing*/ true ;
}
2023-05-09 12:57:56 -04:00
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 ;
}
2023-05-10 15:14:21 -04:00
void FStateTreeDebugger : : AddEvents ( const double StartTime , const double EndTime , const TraceServices : : IFrameProvider & FrameProvider , const IStateTreeTraceProvider & StateTreeTraceProvider )
2023-03-14 13:35:46 -04:00
{
2023-05-09 12:57:56 -04:00
check ( StateTreeAsset . IsValid ( ) ) ;
StateTreeTraceProvider . ReadTimelines ( * StateTreeAsset ,
2023-03-14 13:35:46 -04:00
[ 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 ;
2023-05-09 12:57:56 -04:00
2023-03-14 13:35:46 -04:00
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 ) )
{
2023-05-09 12:57:56 -04:00
FrameProvider . GetFrameFromTime ( TraceFrameType_Game , EventStartTime , Frame ) ;
2023-03-14 13:35:46 -04:00
}
2023-05-09 12:57:56 -04:00
2023-03-14 13:35:46 -04:00
const bool bKeepProcessing = ProcessEvent ( InstanceId , Frame , Event ) ;
return bKeepProcessing ? TraceServices : : EEventEnumerate : : Continue : TraceServices : : EEventEnumerate : : Stop ;
} ) ;
} ) ;
}
2023-05-09 12:57:56 -04:00
# undef LOCTEXT_NAMESPACE
# endif // WITH_STATETREE_DEBUGGER