// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Delegates/Delegate.h" #include "HAL/CriticalSection.h" #include "Templates/Models.h" #include "Memory/MemoryView.h" #include "Misc/Guid.h" #include namespace UE::Telemetry::Private { template constexpr bool HasTelemetryID = false; template constexpr bool HasTelemetryID< DATA_TYPE, std::enable_if_t, FGuid>>> = true; template auto GetDataKeyInternal() { if constexpr (HasTelemetryID) { return DATA_TYPE::TelemetryID; } else { return FGuid{}; } } template FGuid GetDataKey() { static_assert(HasTelemetryID, "Unsupported type for telemetry router, must have a static constexpr FGuid member TelemetryID"); // Silence duplicate compiler error that's less helpful than the static assert if constexpr (HasTelemetryID) { return DATA_TYPE::TelemetryID; } else { return FGuid{}; } } } /** * Provides an interface for routing structured telemetry data between prooducers (engine/editor systems) * and consumers (modules which will format the data for a particular telemetry endpoint) * * Usage: * Declare a unique type for telemetry data. This type needs a static FGuid data member called TelemetryID. * Provide data to any interested aggregators with the method ProvideTelemetry. * Aggregators can register a callback for telemetry data with the method OnTelemetry. * * The guid associated with a type for routing should not be relied on by consumers to be stable across engine versions * or even across different processes, it is only used for runtime routing purposes. It should not be sent to telemetry endpoints. * * @thread_safety Functions are internally synchronized. Re-entrant calls to the public API during callbacks are unsupported. * Data is routed on the same thread it is provided. Users should copy data and schedule tasks to execute on a specified thread * if they require that. */ class FTelemetryRouter { public: FTelemetryRouter(); ~FTelemetryRouter(); static TELEMETRYUTILS_API FTelemetryRouter& Get(); /** * Sends data in a type-safe manner to consumers expecting this data type. * * @param Data Strongly typed data to be sent. Type must implement a static method called GetTelemetryID returning an FGuid. */ template inline void ProvideTelemetry(const DATA_TYPE& Data) { ProvideTelemetryInternal(UE::Telemetry::Private::GetDataKey(), MakeMemoryView(&Data, sizeof(DATA_TYPE))); } /** * Registers a delegate as callback to receive telemetry of a certain type. * * @return a handle that can be used to unregister this sink later so it is no longer called. */ template inline FDelegateHandle OnTelemetry(TDelegate Sink) { check(Sink.IsBound()); FDelegateHandle Handle = Sink.GetHandle(); RegisterTelemetrySinkInternal(UE::Telemetry::Private::GetDataKey(), sizeof(DATA_TYPE), Handle, [Sink=MoveTemp(Sink)](FMemoryView Data) -> bool { return Sink.ExecuteIfBound(*reinterpret_cast(Data.GetData())); }); return Handle; } /** * Registers a callable object as a callback receive telemetry of a certain type. * * @return a handle that can be used to unregister this sink later so it is no longer called. */ template> > inline FDelegateHandle OnTelemetry(CALLABLE&& Sink) { FDelegateHandle Handle{ FDelegateHandle::GenerateNewHandle }; RegisterTelemetrySinkInternal(UE::Telemetry::Private::GetDataKey(), sizeof(DATA_TYPE), Handle, [Sink=MoveTemp(Sink)](FMemoryView Data) -> bool { Sink(*reinterpret_cast(Data.GetData())); return true; }); return Handle; } /** * Removes a previously registered callback. */ template inline void UnregisterTelemetrySink(FDelegateHandle Handle) { UnregisterTelemetrySinkInternal(UE::Telemetry::Private::GetDataKey(), Handle); } private: /** * Register a callback to receive telemetry. The callback should return false if the sink is stale and should be removed from future consideration. */ TELEMETRYUTILS_API void RegisterTelemetrySinkInternal(FGuid Key, SIZE_T ExpectedSize, FDelegateHandle InHandle, TFunction Sink); TELEMETRYUTILS_API void UnregisterTelemetrySinkInternal(FGuid Key, FDelegateHandle InHandle); TELEMETRYUTILS_API void ProvideTelemetryInternal(FGuid Key, FMemoryView Data); TELEMETRYUTILS_API void CheckNotReentrant(); FRWLock SinkLock; struct FSinkSet { SIZE_T DataSize; TMap> Delegates; FSinkSet(SIZE_T InDataSize) : DataSize(InDataSize) { } }; TMap KeyToSinks; };