Files
UnrealEngine/Engine/Source/Developer/TraceInsights/Public/Insights/ViewModels/TimingEventSearch.h
2025-05-18 13:04:45 +08:00

367 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreTypes.h"
#include "Containers/Map.h"
#include "Misc/EnumClassFlags.h"
#include "Templates/SharedPointer.h"
namespace UE::Insights
{
class IFilterExecutor;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Search behavior flags
enum class ETimingEventSearchFlags : int32
{
// Search all matches
None = 0,
SearchAll = None,
// Whether to stop at the first match
StopAtFirstMatch = (1 << 0),
};
ENUM_CLASS_FLAGS(ETimingEventSearchFlags);
////////////////////////////////////////////////////////////////////////////////////////////////////
// A handle to a timing event that was previously searched
struct FTimingEventSearchHandle
{
bool IsValid() const { return Id != uint64(-1); }
void Reset() { Id = uint64(-1); }
bool operator==(const FTimingEventSearchHandle& InOther) const
{
return Id == InOther.Id;
}
static FTimingEventSearchHandle GenerateHandle()
{
static uint64 GlobalId = 0;
FTimingEventSearchHandle ReturnValue;
ReturnValue.Id = GlobalId++;
return ReturnValue;
}
private:
uint64 Id = uint64(-1);
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters for a timing event search
class FTimingEventSearchParameters
{
public:
// Predicate called to filter event matches.
// Note it is assumed that the SearchPredicate (below) will perform basic range checks on StartTime and EndTime,
// therefore this is for any other filtering that needs to take place (e.g. left of an event, below an event etc.)
// Returns true to pass the filter.
typedef TFunctionRef<bool(double /*StartTime*/, double /*EndTime*/, uint32 /*Depth*/)> EventFilterPredicate;
// Predicate called when we get a match
typedef TFunctionRef<void(double /*StartTime*/, double /*EndTime*/, uint32 /*Depth*/)> EventMatchedPredicate;
enum class ESearchDirection : uint32
{
Forward = 0,
Backward = 1,
};
FTimingEventSearchParameters(double InStartTime, double InEndTime, ETimingEventSearchFlags Flags, EventFilterPredicate InEventFilter = NoFilter, EventMatchedPredicate InEventMatched = NoMatch)
: EventFilter(InEventFilter)
, EventMatched(InEventMatched)
, SearchHandle(nullptr)
, StartTime(InStartTime)
, EndTime(InEndTime)
, Flags(Flags)
{}
private:
// Default predicates
static bool NoFilter(double, double, uint32) { return true; }
static void NoMatch(double, double, uint32) {}
public:
// Predicate for search filtering
EventFilterPredicate EventFilter;
// Predicate called when we get a match
EventMatchedPredicate EventMatched;
// A handle to a previous search. This will be written during a search if a cache is utilized and a hit was not generated.
FTimingEventSearchHandle* SearchHandle;
// Start time of the search
double StartTime;
// End time of the search
double EndTime;
// Search behavior flags
ETimingEventSearchFlags Flags;
TSharedPtr<UE::Insights::IFilterExecutor> FilterExecutor;
ESearchDirection SearchDirection = ESearchDirection::Forward;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// Simple acceleration structure used to return previously searched results
template <typename PayloadType, int32 Size = 3>
struct TTimingEventSearchCache
{
public:
TTimingEventSearchCache()
: WriteIndex(0)
{}
// Write to the cache
// @return a handle to the search
FTimingEventSearchHandle Write(double InStartTime, double InEndTime, uint32 InDepth, const PayloadType& InPayload)
{
TPair<FTimingEventSearchHandle, FResultData>& WritePair = CachedValues[WriteIndex];
WritePair.Key = FTimingEventSearchHandle::GenerateHandle();
WritePair.Value.Payload = InPayload;
WritePair.Value.StartTime = InStartTime;
WritePair.Value.EndTime = InEndTime;
WritePair.Value.Depth = InDepth;
WriteIndex = (WriteIndex + 1) % Size;
return WritePair.Key;
}
// Attempt to read from the cache.
// @return false if the value was not found
bool Read(const FTimingEventSearchHandle& InHandle, double& OutStartTime, double& OutEndTime, uint32& OutDepth, PayloadType& OutPayload)
{
static_assert(Size > 1, "TTimingEventSearchCache only works with caches that have multiple values");
if (InHandle.IsValid())
{
// Start read at the previous write, to catch most recently written values
for (int32 ReadIndex = (WriteIndex + (Size - 1)) % Size, ReadCount = 0; ReadCount < Size; ReadCount++, ReadIndex = (ReadIndex + 1) % Size)
{
check(ReadIndex >= 0 && ReadIndex < Size);
const TPair<FTimingEventSearchHandle, FResultData>& CachedPair = CachedValues[ReadIndex];
if (InHandle == CachedPair.Key)
{
OutStartTime = CachedPair.Value.StartTime;
OutEndTime = CachedPair.Value.EndTime;
OutDepth = CachedPair.Value.Depth;
OutPayload = CachedPair.Value.Payload;
return true;
}
}
}
return false;
}
// Reset the cache. Any reads after this will return false
void Reset()
{
for (TPair<FTimingEventSearchHandle, FResultData>& CachedPair : CachedValues)
{
CachedPair.Key.Reset();
}
}
private:
// Current write index
int32 WriteIndex;
// A cached result
struct FResultData
{
PayloadType Payload;
double StartTime;
double EndTime;
uint32 Depth;
};
// Array of cached searches
TPair<FTimingEventSearchHandle, FResultData> CachedValues[Size];
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// Helper used to orchestrate a search of a timing event track's events
// Example of usage:
//
// FMyStruct MatchedPayload;
//
// TTimingEventSearch<FMyStruct>::Search(
// Parameters,
// [](const TTimingEventSearch<FMyStruct>::FContext& InContext)
// {
// for (FMyStruct& Payload : Payloads)
// {
// InContext.Check(InEventStartTime, InEventEndTime, 0, Payload);
// }
// },
// [&MatchedPayload](double InStartTime, double InEndTime, uint32 InDepth, const FMyStruct& InEvent)
// {
// MatchedPayload = InEvent;
// },
// [&MatchedPayload](double InStartTime, double InEndTime, uint32 InDepth)
// {
// // Do something with MatchedPayload, e.g. call a captured lambda.
// });
//
template <typename PayloadType>
struct TTimingEventSearch
{
public:
struct FContext;
// Predicate called when a match has been made to the search parameters
typedef TFunctionRef<void(double /*InStartTime*/, double /*InEndTime*/, uint32 /*InDepth*/, const PayloadType& /*InPayload*/)> PayloadMatchedPredicate;
// Predicate called to filter by payload contents
// Return true to pass the filer
typedef TFunctionRef<bool(double /*InStartTime*/, double /*InEndTime*/, uint32 /*InDepth*/, const PayloadType& /*InPayload*/)> PayloadFilterPredicate;
// Predicate called when a match has been found
typedef TFunctionRef<void(double /*InStartTime*/, double /*InEndTime*/, uint32 /*InDepth*/, const PayloadType& /*InPayload*/)> FoundPredicate;
// Predicate called to run the search, e.g. iterate over an array of events
// It is expected to call FContext::Check on each valid searched event.
typedef TFunctionRef<void(FContext& /*Context*/)> SearchPredicate;
// Context used to operate a search.
struct FContext
{
public:
FContext(const FTimingEventSearchParameters& InParameters, PayloadFilterPredicate InPayloadFilterPredicate, PayloadMatchedPredicate InPayloadMatchedPredicate)
: Parameters(InParameters)
, PayloadMatched(InPayloadMatchedPredicate)
, PayloadFilter(InPayloadFilterPredicate)
, FoundStartTime(-1.0)
, FoundEndTime(-1.0)
, FoundDepth(0)
, bFound(false)
, bContinueSearching(true)
{
}
// Function called to check and potentially match an event
void Check(double InEventStartTime, double InEventEndTime, uint32 InEventDepth, const PayloadType& InEvent)
{
if (bContinueSearching && Parameters.EventFilter(InEventStartTime, InEventEndTime, InEventDepth) && PayloadFilter(InEventStartTime, InEventEndTime, InEventDepth, InEvent))
{
Parameters.EventMatched(InEventStartTime, InEventEndTime, InEventDepth);
PayloadMatched(InEventStartTime, InEventEndTime, InEventDepth, InEvent);
FoundPayload = InEvent;
FoundDepth = InEventDepth;
FoundStartTime = InEventStartTime;
FoundEndTime = InEventEndTime;
bFound = true;
bContinueSearching = (Parameters.Flags & ETimingEventSearchFlags::StopAtFirstMatch) == ETimingEventSearchFlags::None;
}
}
// Access the search parameters
const FTimingEventSearchParameters& GetParameters() const { return Parameters; }
// Accessors for read-only results
const PayloadType& GetPayloadFound() const { return FoundPayload; }
double GetStartTimeFound() const { return FoundStartTime; }
double GetEndTimeFound() const { return FoundEndTime; }
uint32 GetDepthFound() const { return FoundDepth; }
bool IsMatchFound() const { return bFound; }
bool ShouldContinueSearching() const { return bContinueSearching; }
// Allows search to be aborted by predicates
void AbortSearch() { bContinueSearching = false; }
private:
// Search parameters
FTimingEventSearchParameters Parameters;
// Predicate called when an event was matched
PayloadMatchedPredicate PayloadMatched;
// Filter applied to payloads
PayloadFilterPredicate PayloadFilter;
// The payload we have found
PayloadType FoundPayload;
// The start time of the event that was found
double FoundStartTime;
// The end time of the event that was found
double FoundEndTime;
// The depth of the event that was found
uint32 FoundDepth;
// Whether a match was found
bool bFound;
// Internal flag to skip work if a match was found
bool bContinueSearching;
};
public:
// Default predicates
static bool NoFilter(double, double, uint32, const PayloadType&) { return true; }
static void NoMatch(double, double, uint32, const PayloadType&) {}
// Search using only the event filter and no match predicate
static bool Search(const FTimingEventSearchParameters& InParameters, SearchPredicate InSearchPredicate, FoundPredicate InFoundPredicate)
{
return Search(InParameters, InSearchPredicate, NoFilter, InFoundPredicate, NoMatch);
}
// Search using only the event filter, no match predicate and a cache
static bool Search(const FTimingEventSearchParameters& InParameters, SearchPredicate InSearchPredicate, FoundPredicate InFoundPredicate, TTimingEventSearchCache<PayloadType>& InCache)
{
return Search(InParameters, InSearchPredicate, NoFilter, InFoundPredicate, NoMatch, &InCache);
}
// Search using a specific payload filter
static bool Search(const FTimingEventSearchParameters& InParameters, SearchPredicate InSearchPredicate, PayloadFilterPredicate InFilterPredicate, FoundPredicate InFoundPredicate, PayloadMatchedPredicate InPayloadMatchedPredicate, TTimingEventSearchCache<PayloadType>* InCache = nullptr)
{
if (InCache != nullptr && InParameters.SearchHandle != nullptr)
{
double StartTime;
double EndTime;
uint32 Depth;
PayloadType Payload;
if (InCache->Read(*InParameters.SearchHandle, StartTime, EndTime, Depth, Payload))
{
InParameters.EventMatched(StartTime, EndTime, Depth);
InPayloadMatchedPredicate(StartTime, EndTime, Depth, Payload);
InFoundPredicate(StartTime, EndTime, Depth, Payload);
return true;
}
}
FContext Context(InParameters, InFilterPredicate, InPayloadMatchedPredicate);
InSearchPredicate(Context);
if (Context.IsMatchFound())
{
InFoundPredicate(Context.GetStartTimeFound(), Context.GetEndTimeFound(), Context.GetDepthFound(), Context.GetPayloadFound());
if (InCache != nullptr && InParameters.SearchHandle != nullptr)
{
*InParameters.SearchHandle = InCache->Write(Context.GetStartTimeFound(), Context.GetEndTimeFound(), Context.GetDepthFound(), Context.GetPayloadFound());
}
}
return Context.IsMatchFound();
}
};
////////////////////////////////////////////////////////////////////////////////////////////////////