// 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 EventFilterPredicate; // Predicate called when we get a match typedef TFunctionRef 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 FilterExecutor; ESearchDirection SearchDirection = ESearchDirection::Forward; }; //////////////////////////////////////////////////////////////////////////////////////////////////// // Simple acceleration structure used to return previously searched results template 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& 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& 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& 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 CachedValues[Size]; }; //////////////////////////////////////////////////////////////////////////////////////////////////// // Helper used to orchestrate a search of a timing event track's events // Example of usage: // // FMyStruct MatchedPayload; // // TTimingEventSearch::Search( // Parameters, // [](const TTimingEventSearch::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 struct TTimingEventSearch { public: struct FContext; // Predicate called when a match has been made to the search parameters typedef TFunctionRef PayloadMatchedPredicate; // Predicate called to filter by payload contents // Return true to pass the filer typedef TFunctionRef PayloadFilterPredicate; // Predicate called when a match has been found typedef TFunctionRef 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 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& 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* 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(); } }; ////////////////////////////////////////////////////////////////////////////////////////////////////