2023-03-14 13:35:46 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# if WITH_STATETREE_DEBUGGER
# include "Debugger/StateTreeDebugger.h"
# include "Debugger/IStateTreeTraceProvider.h"
# include "Debugger/StateTreeTraceProvider.h"
# include "Debugger/StateTreeTraceTypes.h"
# include "Misc/Paths.h"
# include "Modules/ModuleManager.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 ( )
{
2023-06-22 14:27:48 -04:00
StopSessionAnalysis ( ) ;
2023-03-14 13:35:46 -04:00
}
2023-05-26 11:59:58 -04:00
void FStateTreeDebugger : : Tick ( const float DeltaTime )
2023-03-14 13:35:46 -04:00
{
2023-05-09 12:57:56 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( FStateTreeDebugger : : Tick ) ;
2023-03-17 09:41:13 -04:00
2023-06-09 11:20:16 -04:00
if ( RetryLoadNextLiveSessionTimer > 0.0f )
2023-05-26 11:59:58 -04:00
{
// We are still not connected to the last live session.
// Update polling timer and retry with remaining time; 0 or less will stop retries.
2023-06-09 11:20:16 -04:00
if ( TryStartNewLiveSessionAnalysis ( RetryLoadNextLiveSessionTimer - DeltaTime ) )
2023-05-26 11:59:58 -04:00
{
2023-06-09 11:20:16 -04:00
RetryLoadNextLiveSessionTimer = 0.0f ;
LastLiveSessionId = INDEX_NONE ;
2023-05-26 11:59:58 -04:00
}
}
2023-05-09 12:57:56 -04:00
UpdateInstances ( ) ;
2023-06-22 14:27:48 -04:00
if ( bSessionAnalysisPaused = = false )
{
SyncToCurrentSessionDuration ( ) ;
}
2023-03-14 13:35:46 -04:00
}
2023-06-22 14:27:48 -04:00
void FStateTreeDebugger : : StopSessionAnalysis ( )
2023-03-14 13:35:46 -04:00
{
if ( const TraceServices : : IAnalysisSession * Session = GetAnalysisSession ( ) )
{
Session - > Stop ( true ) ;
2023-07-27 11:23:42 -04:00
AnalysisSession . Reset ( ) ;
2023-03-14 13:35:46 -04:00
}
2023-06-22 14:27:48 -04:00
bSessionAnalysisPaused = false ;
2023-08-04 14:23:12 -04:00
HitBreakpoint . Reset ( ) ;
2023-03-14 13:35:46 -04:00
}
void FStateTreeDebugger : : SyncToCurrentSessionDuration ( )
{
if ( const TraceServices : : IAnalysisSession * Session = GetAnalysisSession ( ) )
{
2023-03-17 09:41:13 -04:00
{
TraceServices : : FAnalysisSessionReadScope SessionReadScope ( * Session ) ;
2023-05-26 11:59:58 -04:00
AnalysisDuration = Session - > GetDurationSeconds ( ) ;
2023-03-17 09:41:13 -04:00
}
2023-05-26 11:59:58 -04:00
ReadTrace ( AnalysisDuration ) ;
2023-03-17 09:41:13 -04:00
}
2023-03-14 13:35:46 -04:00
}
2023-06-09 11:45:15 -04:00
const UE : : StateTreeDebugger : : FInstanceDescriptor * FStateTreeDebugger : : GetInstanceDescriptor ( const FStateTreeInstanceDebugId InstanceId ) const
2023-06-06 11:25:12 -04:00
{
2023-06-09 11:45:15 -04:00
const UE : : StateTreeDebugger : : FInstanceDescriptor * FoundDescriptor = InstanceDescs . FindByPredicate (
[ InstanceId ] ( const UE : : StateTreeDebugger : : FInstanceDescriptor & Descriptor )
2023-06-06 11:25:12 -04:00
{
2023-06-09 11:45:15 -04:00
return Descriptor . Id = = InstanceId ;
2023-06-06 11:25:12 -04:00
} ) ;
2023-06-09 11:45:15 -04:00
return FoundDescriptor ;
2023-06-06 11:25:12 -04:00
}
2023-06-09 11:45:15 -04:00
FText FStateTreeDebugger : : GetInstanceName ( const FStateTreeInstanceDebugId InstanceId ) const
2023-03-14 13:35:46 -04:00
{
2023-06-09 11:45:15 -04:00
const UE : : StateTreeDebugger : : FInstanceDescriptor * FoundDescriptor = GetInstanceDescriptor ( InstanceId ) ;
return ( FoundDescriptor ! = nullptr ) ? FText : : FromString ( FoundDescriptor - > Name ) : LOCTEXT ( " InstanceNotFound " , " Instance not found " ) ;
}
2023-05-09 12:57:56 -04:00
2023-06-09 11:45:15 -04:00
FText FStateTreeDebugger : : GetInstanceDescription ( const FStateTreeInstanceDebugId InstanceId ) const
{
const UE : : StateTreeDebugger : : FInstanceDescriptor * FoundDescriptor = GetInstanceDescriptor ( InstanceId ) ;
return ( FoundDescriptor ! = nullptr ) ? DescribeInstance ( * FoundDescriptor ) : 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
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
2023-07-27 11:23:42 -04:00
SetScrubStateCollectionIndex ( InstanceId . IsValid ( )
? EventCollections . IndexOfByPredicate ( [ InstanceId = InstanceId ] ( const UE : : StateTreeDebugger : : FInstanceEventCollection & Entry )
{
return Entry . InstanceId = = InstanceId ;
} )
2023-06-06 11:25:12 -04:00
: INDEX_NONE ) ;
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
}
}
}
2023-07-27 11:23:42 -04:00
bool FStateTreeDebugger : : RequestAnalysisOfEditorSession ( )
2023-06-09 11:20:16 -04:00
{
2023-06-22 14:27:48 -04:00
// Get snapshot of current trace to help identify the next live one
LastLiveSessionId = INDEX_NONE ;
TArray < FTraceDescriptor > TraceDescriptors ;
GetLiveTraces ( TraceDescriptors ) ;
2023-06-09 11:20:16 -04:00
2023-06-22 14:27:48 -04:00
// 0 is the invalid value used for Trace Id
constexpr int32 InvalidTraceId = 0 ;
int32 ActiveTraceId = InvalidTraceId ;
2023-06-09 11:20:16 -04:00
2023-06-22 14:27:48 -04:00
// StartTraces returns true if a new connection was created. In this case
// we try to start an analysis on that new connection as soon as possible.
// Otherwise it might have been able to use an active connection in which
// case it was returned in the output parameter.
if ( StateTreeModule . StartTraces ( ActiveTraceId ) )
{
// Invalidate our current active session
ActiveSessionTraceDescriptor = FTraceDescriptor ( ) ;
2023-06-09 11:20:16 -04:00
2023-06-22 14:27:48 -04:00
// Stop current analysis if any
StopSessionAnalysis ( ) ;
LastLiveSessionId = TraceDescriptors . Num ( ) ? TraceDescriptors . Last ( ) . TraceId : INDEX_NONE ;
// This won't succeed yet but will schedule our next retry
TryStartNewLiveSessionAnalysis ( 1.0f ) ;
return true ;
}
// Otherwise we start analysis of the already active trace, if any.
if ( ActiveTraceId ! = InvalidTraceId )
{
if ( const FTraceDescriptor * Descriptor = TraceDescriptors . FindByPredicate ( [ ActiveTraceId ] ( const FTraceDescriptor & Descriptor )
{
return Descriptor . TraceId = = ActiveTraceId ;
} ) )
{
2023-07-27 11:23:42 -04:00
return RequestSessionAnalysis ( * Descriptor ) ;
2023-06-22 14:27:48 -04:00
}
}
return false ;
2023-06-09 11:20:16 -04:00
}
bool FStateTreeDebugger : : TryStartNewLiveSessionAnalysis ( const float RetryPollingDuration )
2023-03-14 13:35:46 -04:00
{
TArray < FTraceDescriptor > Traces ;
GetLiveTraces ( Traces ) ;
2023-06-09 11:20:16 -04:00
if ( Traces . Num ( ) & & Traces . Last ( ) . TraceId ! = LastLiveSessionId )
2023-03-14 13:35:46 -04:00
{
2023-07-27 11:23:42 -04:00
// Intentional call to StartSessionAnalysis instead of RequestSessionAnalysis since we want
// to set 'bIsAnalyzingNextEditorSession' before calling OnNewSession delegate.
const bool bStarted = StartSessionAnalysis ( Traces . Last ( ) ) ;
if ( bStarted )
{
UpdateAnalysisTransitionType ( EAnalysisSourceType : : EditorSession ) ;
SetScrubStateCollectionIndex ( INDEX_NONE ) ;
OnNewSession . ExecuteIfBound ( ) ;
}
return bStarted ;
2023-05-26 11:59:58 -04:00
}
2023-06-09 11:20:16 -04:00
RetryLoadNextLiveSessionTimer = RetryPollingDuration ;
ensure ( RetryLoadNextLiveSessionTimer > 0 ) ;
UE_CLOG ( RetryLoadNextLiveSessionTimer > 0 , LogStateTree , Log , TEXT ( " Unable to start analysis for the most recent live session. " ) ) ;
return false ;
2023-03-14 13:35:46 -04:00
}
2023-06-09 11:20:16 -04:00
bool FStateTreeDebugger : : StartSessionAnalysis ( const FTraceDescriptor & TraceDescriptor )
2023-03-14 13:35:46 -04:00
{
if ( ActiveSessionTraceDescriptor = = TraceDescriptor )
{
2023-06-09 11:20:16 -04:00
return ActiveSessionTraceDescriptor . IsValid ( ) ;
2023-03-14 13:35:46 -04:00
}
2023-06-09 11:20:16 -04:00
ActiveSessionTraceDescriptor = FTraceDescriptor ( ) ;
2023-06-22 14:27:48 -04:00
// Make sure any active analysis is stopped
StopSessionAnalysis ( ) ;
2023-03-14 13:35:46 -04:00
UE : : Trace : : FStoreClient * StoreClient = GetStoreClient ( ) ;
if ( StoreClient = = nullptr )
{
2023-06-09 11:20:16 -04:00
return false ;
2023-03-14 13:35:46 -04:00
}
2023-06-22 14:27:48 -04:00
// If new trace descriptor is not valid no need to continue
if ( TraceDescriptor . IsValid ( ) = = false )
{
return false ;
}
2023-03-14 13:35:46 -04:00
2023-05-26 11:59:58 -04:00
RecordingDuration = 0 ;
AnalysisDuration = 0 ;
LastTraceReadTime = 0 ;
2023-03-14 13:35:46 -04:00
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 )
{
2023-06-09 11:20:16 -04:00
return false ;
2023-03-14 13:35:46 -04:00
}
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 ) ) ;
}
2023-06-09 11:20:16 -04:00
if ( AnalysisSession . IsValid ( ) )
{
2023-06-22 14:27:48 -04:00
ActiveSessionTraceDescriptor = TraceDescriptor ;
2023-06-09 11:20:16 -04:00
}
2023-03-14 13:35:46 -04:00
}
2023-06-09 11:20:16 -04:00
return ActiveSessionTraceDescriptor . IsValid ( ) ;
2023-03-14 13:35:46 -04:00
}
2023-07-27 11:23:42 -04:00
void FStateTreeDebugger : : SetScrubStateCollectionIndex ( const int32 EventCollectionIndex )
{
ScrubState . SetEventCollectionIndex ( EventCollectionIndex ) ;
OnScrubStateChanged . Execute ( ScrubState ) ;
RefreshActiveStates ( ) ;
}
2023-03-14 13:35:46 -04:00
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 )
{
2023-06-01 15:05:07 -04:00
if ( ScrubState . SetScrubTime ( ScrubTime ) )
{
OnScrubStateChanged . Execute ( ScrubState ) ;
2023-05-09 12:57:56 -04:00
2023-06-01 15:05:07 -04:00
RefreshActiveStates ( ) ;
}
2023-05-09 12:57:56 -04:00
}
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-06-06 11:25:12 -04:00
const UE : : StateTreeDebugger : : FInstanceEventCollection & EventCollection = EventCollections [ ScrubState . GetEventCollectionIndex ( ) ] ;
const int32 EventIndex = EventCollection . ActiveStatesChanges [ ScrubState . GetActiveStatesIndex ( ) ] . EventIndex ;
2023-05-09 12:57:56 -04:00
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-06-22 14:27:48 -04:00
return ScrubState . HasPreviousFrame ( ) ;
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
{
2023-06-22 14:27:48 -04:00
return ScrubState . HasNextFrame ( ) ;
2023-05-09 12:57:56 -04:00
}
void FStateTreeDebugger : : StepForwardToNextStateWithEvents ( )
{
ScrubState . GotoNextFrame ( ) ;
OnScrubStateChanged . Execute ( ScrubState ) ;
RefreshActiveStates ( ) ;
}
bool FStateTreeDebugger : : CanStepBackToPreviousStateChange ( ) const
{
2023-06-22 14:27:48 -04:00
return ScrubState . HasPreviousActiveStates ( ) ;
2023-05-09 12:57:56 -04:00
}
void FStateTreeDebugger : : StepBackToPreviousStateChange ( )
{
ScrubState . GotoPreviousActiveStates ( ) ;
OnScrubStateChanged . Execute ( ScrubState ) ;
RefreshActiveStates ( ) ;
}
bool FStateTreeDebugger : : CanStepForwardToNextStateChange ( ) const
{
2023-06-22 14:27:48 -04:00
return ScrubState . HasNextActiveStates ( ) ;
2023-05-09 12:57:56 -04:00
}
void FStateTreeDebugger : : StepForwardToNextStateChange ( )
{
ScrubState . GotoNextActiveStates ( ) ;
OnScrubStateChanged . Execute ( ScrubState ) ;
RefreshActiveStates ( ) ;
2023-03-14 13:35:46 -04:00
}
2023-06-16 02:29:34 -04:00
bool FStateTreeDebugger : : HasStateBreakpoint ( const FStateTreeStateHandle StateHandle , const EStateTreeBreakpointType BreakpointType ) const
2023-03-14 13:35:46 -04:00
{
2023-06-16 02:29:34 -04:00
return Breakpoints . ContainsByPredicate ( [ StateHandle , BreakpointType ] ( const FStateTreeDebuggerBreakpoint Breakpoint )
2023-03-14 13:35:46 -04:00
{
2023-06-16 02:29:34 -04:00
if ( Breakpoint . BreakpointType = = BreakpointType )
{
const FStateTreeStateHandle * BreakpointStateHandle = Breakpoint . ElementIdentifier . TryGet < FStateTreeStateHandle > ( ) ;
return ( BreakpointStateHandle ! = nullptr & & * BreakpointStateHandle = = StateHandle ) ;
}
return false ;
} ) ;
}
bool FStateTreeDebugger : : HasTaskBreakpoint ( const FStateTreeIndex16 Index , const EStateTreeBreakpointType BreakpointType ) const
{
return Breakpoints . ContainsByPredicate ( [ Index , BreakpointType ] ( const FStateTreeDebuggerBreakpoint Breakpoint )
{
if ( Breakpoint . BreakpointType = = BreakpointType )
{
const FStateTreeDebuggerBreakpoint : : FStateTreeTaskIndex * BreakpointTaskIndex = Breakpoint . ElementIdentifier . TryGet < FStateTreeDebuggerBreakpoint : : FStateTreeTaskIndex > ( ) ;
return ( BreakpointTaskIndex ! = nullptr & & BreakpointTaskIndex - > Index = = Index ) ;
}
return false ;
} ) ;
}
bool FStateTreeDebugger : : HasTransitionBreakpoint ( const FStateTreeIndex16 Index , const EStateTreeBreakpointType BreakpointType ) const
{
return Breakpoints . ContainsByPredicate ( [ Index , BreakpointType ] ( const FStateTreeDebuggerBreakpoint Breakpoint )
{
if ( Breakpoint . BreakpointType = = BreakpointType )
{
const FStateTreeDebuggerBreakpoint : : FStateTreeTransitionIndex * BreakpointTransitionIndex = Breakpoint . ElementIdentifier . TryGet < FStateTreeDebuggerBreakpoint : : FStateTreeTransitionIndex > ( ) ;
return ( BreakpointTransitionIndex ! = nullptr & & BreakpointTransitionIndex - > Index = = Index ) ;
}
return false ;
} ) ;
}
2023-06-21 10:02:02 -04:00
void FStateTreeDebugger : : SetStateBreakpoint ( const FStateTreeStateHandle StateHandle , const EStateTreeBreakpointType BreakpointType )
2023-06-16 02:29:34 -04:00
{
Breakpoints . Emplace ( StateHandle , BreakpointType ) ;
}
2023-06-21 10:02:02 -04:00
void FStateTreeDebugger : : SetTransitionBreakpoint ( const FStateTreeIndex16 TransitionIndex , const EStateTreeBreakpointType BreakpointType )
{
Breakpoints . Emplace ( FStateTreeDebuggerBreakpoint : : FStateTreeTransitionIndex ( TransitionIndex ) , BreakpointType ) ;
}
void FStateTreeDebugger : : SetTaskBreakpoint ( const FStateTreeIndex16 NodeIndex , const EStateTreeBreakpointType BreakpointType )
2023-06-16 02:29:34 -04:00
{
2023-06-16 03:31:08 -04:00
Breakpoints . Emplace ( FStateTreeDebuggerBreakpoint : : FStateTreeTaskIndex ( NodeIndex ) , BreakpointType ) ;
2023-06-16 02:29:34 -04:00
}
2023-06-21 10:02:02 -04:00
void FStateTreeDebugger : : ClearBreakpoint ( const FStateTreeIndex16 NodeIndex , const EStateTreeBreakpointType BreakpointType )
2023-06-16 02:29:34 -04:00
{
const int32 Index = Breakpoints . IndexOfByPredicate ( [ NodeIndex , BreakpointType ] ( const FStateTreeDebuggerBreakpoint & Breakpoint )
{
const FStateTreeDebuggerBreakpoint : : FStateTreeTaskIndex * IndexPtr = Breakpoint . ElementIdentifier . TryGet < FStateTreeDebuggerBreakpoint : : FStateTreeTaskIndex > ( ) ;
return ( IndexPtr ! = nullptr & & IndexPtr - > Index = = NodeIndex & & Breakpoint . BreakpointType = = BreakpointType ) ;
2023-03-14 13:35:46 -04:00
} ) ;
2023-06-16 02:29:34 -04:00
if ( Index ! = INDEX_NONE )
2023-03-14 13:35:46 -04:00
{
2023-06-16 02:29:34 -04:00
Breakpoints . RemoveAtSwap ( Index ) ;
2023-03-14 13:35:46 -04:00
}
2023-06-16 02:29:34 -04:00
}
2023-03-14 13:35:46 -04:00
2023-06-16 02:29:34 -04:00
void FStateTreeDebugger : : ClearAllBreakpoints ( )
{
Breakpoints . Empty ( ) ;
2023-03-14 13:35:46 -04:00
}
const TraceServices : : IAnalysisSession * FStateTreeDebugger : : GetAnalysisSession ( ) const
{
return AnalysisSession . Get ( ) ;
}
2023-07-27 11:23:42 -04:00
bool FStateTreeDebugger : : RequestSessionAnalysis ( const FTraceDescriptor & TraceDescriptor )
{
if ( StartSessionAnalysis ( TraceDescriptor ) )
{
UpdateAnalysisTransitionType ( EAnalysisSourceType : : SelectedSession ) ;
SetScrubStateCollectionIndex ( INDEX_NONE ) ;
OnNewSession . ExecuteIfBound ( ) ;
return true ;
}
return false ;
}
void FStateTreeDebugger : : UpdateAnalysisTransitionType ( const EAnalysisSourceType SourceType )
{
switch ( AnalysisTransitionType )
{
case EAnalysisTransitionType : : Unset :
AnalysisTransitionType = ( SourceType = = EAnalysisSourceType : : SelectedSession )
? EAnalysisTransitionType : : NoneToSelected
: EAnalysisTransitionType : : NoneToEditor ;
break ;
case EAnalysisTransitionType : : NoneToSelected :
case EAnalysisTransitionType : : EditorToSelected :
case EAnalysisTransitionType : : SelectedToSelected :
AnalysisTransitionType = ( SourceType = = EAnalysisSourceType : : SelectedSession )
? EAnalysisTransitionType : : SelectedToSelected
: EAnalysisTransitionType : : SelectedToEditor ;
break ;
case EAnalysisTransitionType : : NoneToEditor :
case EAnalysisTransitionType : : EditorToEditor :
case EAnalysisTransitionType : : SelectedToEditor :
AnalysisTransitionType = ( SourceType = = EAnalysisSourceType : : SelectedSession )
? EAnalysisTransitionType : : EditorToSelected
: EAnalysisTransitionType : : EditorToEditor ;
break ;
default :
ensureMsgf ( false , TEXT ( " Unhandled transition type. " ) ) ;
}
}
2023-03-14 13:35:46 -04:00
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
}
2023-06-27 15:58:50 -04:00
if ( HitBreakpoint . IsSet ( ) )
2023-03-17 09:41:13 -04:00
{
2023-06-27 15:58:50 -04:00
check ( HitBreakpoint . InstanceId . IsValid ( ) ) ;
check ( Breakpoints . IsValidIndex ( HitBreakpoint . Index ) ) ;
2023-05-09 12:57:56 -04:00
2023-06-21 10:02:02 -04:00
// Force scrub time to latest simulation time to reflect most recent events.
2023-05-23 10:46:16 -04:00
// This will notify scrub position changed and active states
2023-06-27 15:58:50 -04:00
SetScrubTime ( HitBreakpoint . Time ) ;
2023-05-23 10:46:16 -04:00
2023-05-09 12:57:56 -04:00
// Make sure the instance is selected in case the breakpoint was set for any instances
2023-06-27 15:58:50 -04:00
if ( SelectedInstanceId ! = HitBreakpoint . InstanceId )
2023-05-09 12:57:56 -04:00
{
2023-06-27 15:58:50 -04:00
SelectInstance ( HitBreakpoint . InstanceId ) ;
2023-05-09 12:57:56 -04:00
}
2023-06-27 15:58:50 -04:00
OnBreakpointHit . ExecuteIfBound ( HitBreakpoint . InstanceId , Breakpoints [ HitBreakpoint . Index ] ) ;
2023-03-17 09:41:13 -04:00
2023-06-22 14:27:48 -04:00
PauseSessionAnalysis ( ) ;
2023-03-17 09:41:13 -04:00
}
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 ) ;
2023-05-26 11:59:58 -04:00
if ( LastTraceReadTime = = 0 | | ( bValidLastReadFrame & & Frame . Index > LastReadFrame . Index ) )
2023-03-14 13:35:46 -04:00
{
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
}
}
}
2023-06-22 14:27:48 -04:00
bool FStateTreeDebugger : : EvaluateBreakpoints ( const FStateTreeInstanceDebugId InstanceId , const FStateTreeTraceEventVariantType & Event )
2023-03-14 13:35:46 -04:00
{
2023-06-22 14:27:48 -04:00
if ( StateTreeAsset = = nullptr // asset is required to properly match state handles
2023-06-27 15:58:50 -04:00
| | HitBreakpoint . IsSet ( ) // Only consider first hit breakpoint in the frame
2023-06-16 02:29:34 -04:00
| | Breakpoints . IsEmpty ( )
| | ( SelectedInstanceId . IsValid ( ) & & InstanceId ! = SelectedInstanceId ) ) // ignore events not for the selected instances
2023-03-14 13:35:46 -04:00
{
2023-06-22 14:27:48 -04:00
return false ;
2023-06-16 02:29:34 -04:00
}
2023-05-09 12:57:56 -04:00
2023-06-16 02:29:34 -04:00
for ( int BreakpointIndex = 0 ; BreakpointIndex < Breakpoints . Num ( ) ; + + BreakpointIndex )
{
const FStateTreeDebuggerBreakpoint Breakpoint = Breakpoints [ BreakpointIndex ] ;
2023-06-27 15:58:50 -04:00
if ( Breakpoint . IsMatchingEvent ( Event ) )
2023-06-16 02:29:34 -04:00
{
2023-06-27 15:58:50 -04:00
HitBreakpoint . Index = BreakpointIndex ;
HitBreakpoint . InstanceId = InstanceId ;
HitBreakpoint . Time = RecordingDuration ;
2023-03-14 13:35:46 -04:00
}
2023-05-09 12:57:56 -04:00
}
2023-06-22 14:27:48 -04:00
2023-06-27 15:58:50 -04:00
return HitBreakpoint . IsSet ( ) ;
2023-06-16 02:29:34 -04:00
}
2023-03-14 13:35:46 -04:00
2023-06-16 02:29:34 -04:00
bool FStateTreeDebugger : : ProcessEvent ( const FStateTreeInstanceDebugId InstanceId , const TraceServices : : FFrame & Frame , const FStateTreeTraceEventVariantType & Event )
{
UE : : StateTreeDebugger : : FInstanceEventCollection * ExistingCollection = EventCollections . FindByPredicate (
[ InstanceId ] ( const UE : : StateTreeDebugger : : 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 ) ;
2023-07-27 11:23:42 -04:00
ExistingCollection = & EventCollections . Emplace_GetRef ( InstanceId ) ;
2023-05-09 12:57:56 -04:00
// 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)
2023-06-06 11:25:12 -04:00
if ( SelectedInstanceId = = InstanceId & & ScrubState . GetEventCollectionIndex ( ) = = INDEX_NONE )
2023-05-09 12:57:56 -04:00
{
2023-07-27 11:23:42 -04:00
SetScrubStateCollectionIndex ( EventCollections . Num ( ) - 1 ) ;
2023-05-09 12:57:56 -04:00
}
}
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 )
{
2023-05-26 11:59:58 -04:00
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 ( ) ) ) ;
2023-05-09 12:57:56 -04:00
}
// 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. " ) ) ;
2023-05-26 11:59:58 -04:00
const int32 FrameSpanIndex = ExistingCollection - > FrameSpans . Num ( ) - 1 ;
2023-05-09 12:57:56 -04:00
// Add new entry for the first event or if the last event is for a different frame
if ( ExistingCollection - > ActiveStatesChanges . IsEmpty ( )
2023-05-26 11:59:58 -04:00
| | ExistingCollection - > ActiveStatesChanges . Last ( ) . SpanIndex ! = FrameSpanIndex )
2023-05-09 12:57:56 -04:00
{
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
2023-05-26 11:59:58 -04:00
ExistingCollection - > ActiveStatesChanges . Last ( ) . EventIndex = 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-06-27 15:58:50 -04:00
// Process at the end so RecordingDuration is up to date and we can associate it to a hit breakpoint if necessary.
EvaluateBreakpoints ( InstanceId , 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-07-27 11:23:42 -04:00
void FStateTreeDebugger : : ResetEventCollections ( )
{
EventCollections . Reset ( ) ;
SetScrubStateCollectionIndex ( INDEX_NONE ) ;
}
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 )
{
2023-06-09 13:20:39 -04:00
bool bValidFrame = true ;
2023-03-14 13:35:46 -04:00
// 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-06-09 13:20:39 -04:00
bValidFrame = FrameProvider . GetFrameFromTime ( TraceFrameType_Game , EventStartTime , Frame ) ;
if ( bValidFrame = = false )
{
// Edge case for events from a missing first complete frame.
// (i.e. FrameProvider didn't get BeginFrame event but StateTreeEvent were sent in that frame)
// Doing this will merge our two first frames of state tree events using the same recording world time
// but this should happen only for late start recording.
const TraceServices : : FFrame * FirstFrame = FrameProvider . GetFrame ( TraceFrameType_Game , 0 ) ;
if ( FirstFrame ! = nullptr & & EventEndTime < FirstFrame - > StartTime )
{
Frame = * FirstFrame ;
bValidFrame = true ;
}
}
2023-03-14 13:35:46 -04:00
}
2023-05-09 12:57:56 -04:00
2023-06-09 13:20:39 -04:00
if ( bValidFrame )
{
const bool bKeepProcessing = ProcessEvent ( InstanceId , Frame , Event ) ;
return bKeepProcessing ? TraceServices : : EEventEnumerate : : Continue : TraceServices : : EEventEnumerate : : Stop ;
}
// Skip events outside of game frames
return TraceServices : : EEventEnumerate : : Continue ;
2023-03-14 13:35:46 -04:00
} ) ;
} ) ;
}
2023-05-09 12:57:56 -04:00
# undef LOCTEXT_NAMESPACE
# endif // WITH_STATETREE_DEBUGGER