ITelemetrySink with the null, log and JSON Lines sinks, FTelemetryEvent and the envelope, TelemetryEvents, UTelemetrySubsystem on the game instance, ASaltyGameMode minting the session id into the replicated ASaltyGameState, bs.TelemetryTest, the git hash in the build string (D-42) and four Salty.Core.Telemetry tests replacing the placeholder. Proved standalone and with a headless server plus client sharing one session id. Also enables the engine's Editor, AutomationTest, GameplayTags, ConfigSettings and LiveCoding MCP toolsets (D-43) so the editor's MCP server exposes more than the skills toolset. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
60 lines
3.0 KiB
C++
60 lines
3.0 KiB
C++
#pragma once
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "Dom/JsonObject.h"
|
|
|
|
// One emission. Call sites fill Name (from TelemetryEvents) and Payload; the subsystem stamps the envelope.
|
|
struct SALTYCORE_API FTelemetryEvent
|
|
{
|
|
FName Name;
|
|
TSharedPtr<FJsonObject> Payload;
|
|
};
|
|
|
|
// Stamped by the subsystem onto every event. Serialised with snake_case keys; see Telemetry::ToJsonLine.
|
|
struct SALTYCORE_API FTelemetryEnvelope
|
|
{
|
|
int32 SchemaVersion = 1;
|
|
FGuid EventId; // unique per emission, for de-duplication
|
|
FDateTime TimestampUtc;
|
|
float GameTime = 0.f; // seconds since the world began; zero when there is no world yet
|
|
FGuid SessionId; // minted by the server, adopted by clients on join; invalid before a session
|
|
FString PlayerId; // hashed server-minted id; empty on the server
|
|
bool bIsServer = false; // authoritative events versus observed ones
|
|
int32 PartySize = 0;
|
|
FString Build; // FApp::GetBuildVersion() plus the git hash
|
|
FString Map;
|
|
bool bCheatsUsed = false; // sticky true after the first cheat this session
|
|
};
|
|
|
|
// A small fluent builder for payloads, so a call site reads as one expression:
|
|
// Telemetry->Emit(TelemetryEvents::CheatUsed, FTelemetryPayload().Set(TEXT("command"), Command));
|
|
class SALTYCORE_API FTelemetryPayload
|
|
{
|
|
public:
|
|
FTelemetryPayload() : Object(MakeShared<FJsonObject>()) {}
|
|
|
|
FTelemetryPayload& Set(const FString& Key, const FString& Value) { Object->SetStringField(Key, Value); return *this; }
|
|
FTelemetryPayload& Set(const FString& Key, const TCHAR* Value) { Object->SetStringField(Key, Value); return *this; }
|
|
FTelemetryPayload& Set(const FString& Key, FName Value) { Object->SetStringField(Key, Value.ToString()); return *this; }
|
|
FTelemetryPayload& Set(const FString& Key, const FGuid& Value) { Object->SetStringField(Key, Value.ToString(EGuidFormats::DigitsWithHyphensLower)); return *this; }
|
|
FTelemetryPayload& Set(const FString& Key, double Value) { Object->SetNumberField(Key, Value); return *this; }
|
|
FTelemetryPayload& Set(const FString& Key, float Value) { Object->SetNumberField(Key, Value); return *this; }
|
|
FTelemetryPayload& Set(const FString& Key, int32 Value) { Object->SetNumberField(Key, Value); return *this; }
|
|
FTelemetryPayload& Set(const FString& Key, int64 Value) { Object->SetNumberField(Key, static_cast<double>(Value)); return *this; }
|
|
FTelemetryPayload& Set(const FString& Key, bool Value) { Object->SetBoolField(Key, Value); return *this; }
|
|
|
|
TSharedPtr<FJsonObject> Build() const { return Object; }
|
|
operator TSharedPtr<FJsonObject>() const { return Object; }
|
|
|
|
private:
|
|
TSharedPtr<FJsonObject> Object;
|
|
};
|
|
|
|
namespace Telemetry
|
|
{
|
|
// The one serialisation of an event: the envelope's fields as snake_case keys, then "name" and "payload"
|
|
// (an empty object when the event has none), condensed to a single line with no trailing newline. Pure, so
|
|
// the sinks share it and the tests can check it without a sink.
|
|
SALTYCORE_API FString ToJsonLine(const FTelemetryEnvelope& Envelope, const FTelemetryEvent& Event);
|
|
}
|