Skip to content

Examples

Practical examples of instrumenting common Unreal Engine scenarios with Micromegas.

Basic Setup

Game Instance Initialization

Complete setup with authentication and default context:

// MyGameInstance.h
UCLASS()
class MYGAME_API UMyGameInstance : public UGameInstance
{
    GENERATED_BODY()

public:
    virtual void Init() override;
    virtual void Shutdown() override;

private:
    void SetupTelemetryContext();
};

// MyGameInstance.cpp
#include "MyGameInstance.h"
#include "MicromegasTelemetrySink/MicromegasTelemetrySinkModule.h"
#include "MicromegasTracing/Macros.h"
#include "MicromegasTracing/Dispatch.h"
#include "MicromegasTracing/DefaultContext.h"

void UMyGameInstance::Init()
{
    Super::Init();

    // Create auth provider
    class FSimpleAuthProvider : public ITelemetryAuthenticator
    {
    public:
        virtual ~FSimpleAuthProvider() = default;

        virtual void Init(const MicromegasTracing::EventSinkPtr& InSink) override {}

        virtual bool IsReady() override { return true; }

        virtual bool Sign(IHttpRequest& Request) override
        {
            FString ApiKey = GetDefault<UGameSettings>()->TelemetryApiKey;
            Request.SetHeader(TEXT("Authorization"), TEXT("Bearer ") + ApiKey);
            return true;
        }
    };

    // Initialize telemetry
    auto Auth = MakeShared<FSimpleAuthProvider>();
    IMicromegasTelemetrySinkModule::LoadModuleChecked().InitTelemetry(
        TEXT("https://telemetry.example.com:9000"),
        Auth
    );

    SetupTelemetryContext();

    MICROMEGAS_LOG("Game", MicromegasTracing::LogLevel::Info, 
                   TEXT("Game instance initialized"));
}

void UMyGameInstance::SetupTelemetryContext()
{
    if (auto* Ctx = MicromegasTracing::Dispatch::GetDefaultContext())
    {
        // Session info
        Ctx->Set(FName("session_id"), FName(*FGuid::NewGuid().ToString()));
        Ctx->Set(FName("timestamp"), FName(*FDateTime::UtcNow().ToString()));

        // Build info
        Ctx->Set(FName("build_version"), FName(TEXT(GAME_VERSION)));
        Ctx->Set(FName("build_config"), FName(TEXT(STRINGIFY(UE_BUILD_CONFIGURATION))));

        // Platform info
        Ctx->Set(FName("platform"), FName(*UGameplayStatics::GetPlatformName()));
        Ctx->Set(FName("cpu"), FName(*FPlatformMisc::GetCPUBrand()));
        Ctx->Set(FName("gpu"), FName(*GRHIAdapterName));

        // Player info (if available)
        if (ULocalPlayer* LocalPlayer = GetFirstGamePlayer())
        {
            Ctx->Set(FName("player_id"), FName(*LocalPlayer->GetPreferredUniqueNetId().ToString()));
        }
    }
}

void UMyGameInstance::Shutdown()
{
    MICROMEGAS_LOG("Game", MicromegasTracing::LogLevel::Info, 
                   TEXT("Game instance shutting down"));

    // Force flush before shutdown
    MicromegasTracing::Dispatch::FlushLogStream();
    MicromegasTracing::Dispatch::FlushMetricStream();
    MicromegasTracing::Dispatch::FlushCurrentThreadStream();

    Super::Shutdown();
}

Game Loop Instrumentation

Game Mode with Performance Metrics

// MyGameMode.cpp
void AMyGameMode::Tick(float DeltaSeconds)
{
    MICROMEGAS_SPAN_FUNCTION("Game.GameMode");

    Super::Tick(DeltaSeconds);

    // Frame metrics
    MICROMEGAS_FMETRIC("Performance", MicromegasTracing::Verbosity::Med,
                       TEXT("FrameTime"), TEXT("ms"), DeltaSeconds * 1000.0f);

    MICROMEGAS_FMETRIC("Performance", MicromegasTracing::Verbosity::Med,
                       TEXT("FPS"), TEXT("fps"), 1.0f / DeltaSeconds);

    // Game state metrics
    MICROMEGAS_IMETRIC("Game", MicromegasTracing::Verbosity::Low,
                       TEXT("PlayerCount"), TEXT("count"), 
                       GetNumPlayers());

    MICROMEGAS_IMETRIC("Game", MicromegasTracing::Verbosity::Low,
                       TEXT("AICount"), TEXT("count"),
                       GetWorld()->GetNumPawns() - GetNumPlayers());

    // Memory metrics (every 60 frames)
    static int32 FrameCounter = 0;
    if (++FrameCounter % 60 == 0)
    {
        FPlatformMemoryStats MemStats = FPlatformMemory::GetStats();
        MICROMEGAS_IMETRIC("Memory", MicromegasTracing::Verbosity::Low,
                           TEXT("WorkingSetSize"), TEXT("bytes"),
                           MemStats.UsedPhysical);
    }
}

void AMyGameMode::HandleMatchIsWaitingToStart()
{
    MICROMEGAS_LOG("Game.Match", MicromegasTracing::LogLevel::Info,
                   TEXT("Match waiting to start"));
    Super::HandleMatchIsWaitingToStart();
}

void AMyGameMode::HandleMatchHasStarted()
{
    MICROMEGAS_SPAN_FUNCTION("Game.Match");
    MICROMEGAS_LOG("Game.Match", MicromegasTracing::LogLevel::Info,
                   FString::Printf(TEXT("Match started on map: %s"), 
                   *GetWorld()->GetMapName()));

    // Update context with match info
    if (auto* Ctx = MicromegasTracing::Dispatch::GetDefaultContext())
    {
        Ctx->Set(FName("match_id"), FName(*FGuid::NewGuid().ToString()));
        Ctx->Set(FName("map"), FName(*GetWorld()->GetMapName()));
        Ctx->Set(FName("game_mode"), FName(*GetClass()->GetName()));
    }

    Super::HandleMatchHasStarted();
}

Actor and Component Lifecycle

Instrumented Actor

// MyActor.cpp
#include "MicromegasTracing/Macros.h"

void AMyActor::BeginPlay()
{
    MICROMEGAS_SPAN_UOBJECT("Actor.Lifecycle", this);

    Super::BeginPlay();

    MICROMEGAS_LOG("Actor", MicromegasTracing::LogLevel::Debug,
                   FString::Printf(TEXT("%s spawned at %s"), 
                   *GetName(), *GetActorLocation().ToString()));

    // Track actor spawns by class
    MICROMEGAS_IMETRIC("Actor", MicromegasTracing::Verbosity::Med,
                       *FString::Printf(TEXT("Spawned.%s"), *GetClass()->GetName()),
                       TEXT("count"), 1);
}

void AMyActor::Tick(float DeltaTime)
{
    // Only trace tick for important actors
    if (bIsImportant)
    {
        MICROMEGAS_SPAN_UOBJECT("Actor.Tick", this);
        Super::Tick(DeltaTime);

        // Actor-specific logic...
    }
    else
    {
        Super::Tick(DeltaTime);
    }
}

void AMyActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    MICROMEGAS_LOG("Actor", MicromegasTracing::LogLevel::Debug,
                   FString::Printf(TEXT("%s destroyed: %s"), 
                   *GetName(), 
                   *UEnum::GetValueAsString(EndPlayReason)));

    Super::EndPlay(EndPlayReason);
}

float AMyActor::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent,
                           AController* EventInstigator, AActor* DamageCauser)
{
    float ActualDamage = Super::TakeDamage(DamageAmount, DamageEvent, 
                                           EventInstigator, DamageCauser);

    MICROMEGAS_LOG("Combat", MicromegasTracing::LogLevel::Info,
                   FString::Printf(TEXT("%s took %.1f damage from %s"), 
                   *GetName(), ActualDamage,
                   DamageCauser ? *DamageCauser->GetName() : TEXT("Unknown")));

    MICROMEGAS_FMETRIC("Combat", MicromegasTracing::Verbosity::High,
                       TEXT("DamageDealt"), TEXT("points"), ActualDamage);

    return ActualDamage;
}

Player Controller

Player Actions and Input

// MyPlayerController.cpp
void AMyPlayerController::BeginPlay()
{
    Super::BeginPlay();

    if (auto* Ctx = MicromegasTracing::Dispatch::GetDefaultContext())
    {
        // Set player-specific context
        Ctx->Set(FName("player_name"), FName(*PlayerState->GetPlayerName()));
        Ctx->Set(FName("player_id"), FName(*GetUniqueID().ToString()));
    }

    MICROMEGAS_LOG("Player", MicromegasTracing::LogLevel::Info,
                   FString::Printf(TEXT("Player %s joined"), 
                   *PlayerState->GetPlayerName()));
}

void AMyPlayerController::SetupInputComponent()
{
    Super::SetupInputComponent();

    InputComponent->BindAction("Fire", IE_Pressed, this, &AMyPlayerController::OnFire);
    InputComponent->BindAction("Jump", IE_Pressed, this, &AMyPlayerController::OnJump);
    InputComponent->BindAction("Interact", IE_Pressed, this, &AMyPlayerController::OnInteract);
}

void AMyPlayerController::OnFire()
{
    MICROMEGAS_LOG("Player.Input", MicromegasTracing::LogLevel::Trace,
                   TEXT("Fire action"));
    MICROMEGAS_IMETRIC("Player.Actions", MicromegasTracing::Verbosity::High,
                       TEXT("Fire"), TEXT("count"), 1);

    // Fire logic...
}

void AMyPlayerController::OnJump()
{
    MICROMEGAS_IMETRIC("Player.Actions", MicromegasTracing::Verbosity::High,
                       TEXT("Jump"), TEXT("count"), 1);
    // Jump logic...
}

void AMyPlayerController::OnInteract()
{
    MICROMEGAS_SPAN_SCOPE("Player.Interaction", "Interact");

    FHitResult HitResult;
    if (GetHitResultUnderCursor(ECC_Pawn, false, HitResult))
    {
        if (AActor* HitActor = HitResult.GetActor())
        {
            MICROMEGAS_LOG("Player.Interaction", MicromegasTracing::LogLevel::Info,
                           FString::Printf(TEXT("Interacting with %s"), 
                           *HitActor->GetName()));

            // Interaction logic...
        }
    }
}

Network Replication

Network Metrics and Events

// MyGameState.cpp
void AMyGameState::Tick(float DeltaSeconds)
{
    Super::Tick(DeltaSeconds);

    // Network metrics (every second)
    TimeSinceLastNetworkUpdate += DeltaSeconds;
    if (TimeSinceLastNetworkUpdate >= 1.0f)
    {
        TimeSinceLastNetworkUpdate = 0.0f;

        if (UNetDriver* NetDriver = GetWorld()->GetNetDriver())
        {
            MICROMEGAS_IMETRIC("Network", MicromegasTracing::Verbosity::Med,
                               TEXT("ClientConnections"), TEXT("count"),
                               NetDriver->ClientConnections.Num());

            MICROMEGAS_IMETRIC("Network", MicromegasTracing::Verbosity::Med,
                               TEXT("TotalNetObjects"), TEXT("count"),
                               NetDriver->GetNetworkObjectList().GetObjects().Num());

            // Bandwidth metrics
            MICROMEGAS_IMETRIC("Network", MicromegasTracing::Verbosity::Med,
                               TEXT("InBytes"), TEXT("bytes"),
                               NetDriver->InBytes);

            MICROMEGAS_IMETRIC("Network", MicromegasTracing::Verbosity::Med,
                               TEXT("OutBytes"), TEXT("bytes"),
                               NetDriver->OutBytes);
        }
    }
}

// RPC tracking
void AMyGameState::ServerRPC_Implementation(const FString& Data)
{
    MICROMEGAS_SPAN_SCOPE("Network.RPC", "ServerRPC");
    MICROMEGAS_IMETRIC("Network.RPC", MicromegasTracing::Verbosity::High,
                       TEXT("ServerCalls"), TEXT("count"), 1);

    // Process RPC...
}

void AMyGameState::ClientRPC_Implementation(const FString& Data)
{
    MICROMEGAS_IMETRIC("Network.RPC", MicromegasTracing::Verbosity::High,
                       TEXT("ClientCalls"), TEXT("count"), 1);

    // Process RPC...
}

void AMyGameState::OnRep_ReplicatedProperty()
{
    MICROMEGAS_IMETRIC("Network.Replication", MicromegasTracing::Verbosity::High,
                       TEXT("PropertyUpdates"), TEXT("count"), 1);
}

Asset Loading and Streaming

Content Loading Instrumentation

// MyAssetManager.cpp
void UMyAssetManager::LoadAssetAsync(const FString& AssetPath)
{
    MICROMEGAS_SPAN_NAME("Content.AsyncLoad", *AssetPath);

    MICROMEGAS_LOG("Content", MicromegasTracing::LogLevel::Debug,
                   FString::Printf(TEXT("Loading asset: %s"), *AssetPath));

    FStreamableManager& Streamable = UAssetManager::GetStreamableManager();
    TSharedPtr<FStreamableHandle> Handle = Streamable.RequestAsyncLoad(
        FSoftObjectPath(AssetPath),
        FStreamableDelegate::CreateLambda([AssetPath]()
        {
            MICROMEGAS_LOG("Content", MicromegasTracing::LogLevel::Debug,
                           FString::Printf(TEXT("Asset loaded: %s"), *AssetPath));

            MICROMEGAS_IMETRIC("Content", MicromegasTracing::Verbosity::Med,
                               TEXT("AssetsLoaded"), TEXT("count"), 1);
        })
    );
}

void UMyAssetManager::OnLevelStreamingComplete(ULevelStreaming* StreamedLevel)
{
    if (StreamedLevel && StreamedLevel->GetLoadedLevel())
    {
        int64 SizeBytes = StreamedLevel->GetLoadedLevel()->GetOutermost()->GetFileSize();

        MICROMEGAS_LOG("Content.Streaming", MicromegasTracing::LogLevel::Info,
                       FString::Printf(TEXT("Level streamed: %s (%.2f MB)"), 
                       *StreamedLevel->GetWorldAssetPackageFName().ToString(),
                       SizeBytes / (1024.0f * 1024.0f)));

        MICROMEGAS_IMETRIC("Content.Streaming", MicromegasTracing::Verbosity::Low,
                           TEXT("LevelSize"), TEXT("bytes"), SizeBytes);
    }
}

AI and Behavior Trees

AI Controller Instrumentation

// MyAIController.cpp
void AMyAIController::RunBehaviorTree(UBehaviorTree* BTAsset)
{
    MICROMEGAS_SPAN_SCOPE("AI.BehaviorTree", "RunTree");

    MICROMEGAS_LOG("AI", MicromegasTracing::LogLevel::Debug,
                   FString::Printf(TEXT("Starting behavior tree: %s"), 
                   *BTAsset->GetName()));

    return Super::RunBehaviorTree(BTAsset);
}

void AMyAIController::OnMoveCompleted(FAIRequestID RequestID, EPathFollowingResult::Type Result)
{
    MICROMEGAS_LOG("AI.Movement", MicromegasTracing::LogLevel::Trace,
                   FString::Printf(TEXT("AI move completed: %s"), 
                   *UEnum::GetValueAsString(Result)));

    MICROMEGAS_IMETRIC("AI.Movement", MicromegasTracing::Verbosity::High,
                       TEXT("MovesCompleted"), TEXT("count"), 1);

    Super::OnMoveCompleted(RequestID, Result);
}

// BTTask instrumentation
EBTNodeResult::Type UMyBTTask::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
    MICROMEGAS_SPAN_SCOPE("AI.BTTask", GetNodeName());

    EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);

    MICROMEGAS_LOG("AI.BehaviorTree", MicromegasTracing::LogLevel::Trace,
                   FString::Printf(TEXT("Task %s: %s"), 
                   *GetNodeName(),
                   *UEnum::GetValueAsString(Result)));

    return Result;
}

Profiling Critical Paths

Render Thread Instrumentation

// MySceneProxy.cpp
void FMySceneProxy::GetDynamicMeshElements(...)
{
    MICROMEGAS_SPAN_SCOPE("Render.SceneProxy", "GetDynamicMeshElements");

    // Expensive rendering operations
    MICROMEGAS_IMETRIC("Render", MicromegasTracing::Verbosity::High,
                       TEXT("DynamicElements"), TEXT("count"), Elements.Num());
}

Physics Simulation

// MyPhysicsActor.cpp
void AMyPhysicsActor::SimulatePhysics(float DeltaTime)
{
    MICROMEGAS_SPAN_FUNCTION("Physics.Simulation");

    double StartTime = FPlatformTime::Seconds();

    // Run physics simulation
    RunComplexPhysicsSimulation(DeltaTime);

    double SimTime = (FPlatformTime::Seconds() - StartTime) * 1000.0;
    MICROMEGAS_FMETRIC("Physics", MicromegasTracing::Verbosity::Med,
                       TEXT("SimulationTime"), TEXT("ms"), SimTime);

    if (SimTime > 16.0) // Longer than a frame
    {
        MICROMEGAS_LOG("Physics", MicromegasTracing::LogLevel::Warn,
                       FString::Printf(TEXT("Physics simulation took %.2fms"), SimTime));
    }
}

Error Handling and Debugging

Comprehensive Error Logging

void UMyGameSubsystem::HandleError(const FString& ErrorContext, const FString& ErrorMessage)
{
    // Log the error
    MICROMEGAS_LOG("Error", MicromegasTracing::LogLevel::Error,
                   FString::Printf(TEXT("[%s] %s"), *ErrorContext, *ErrorMessage));

    // Track error metrics
    MICROMEGAS_IMETRIC("Errors", MicromegasTracing::Verbosity::Low,
                       *FString::Printf(TEXT("Error.%s"), *ErrorContext),
                       TEXT("count"), 1);

    // Add error to context for correlation
    if (auto* Ctx = MicromegasTracing::Dispatch::GetDefaultContext())
    {
        Ctx->Set(FName("last_error"), FName(*ErrorMessage));
        Ctx->Set(FName("error_time"), FName(*FDateTime::UtcNow().ToString()));
    }

    // Force flush for critical errors
    if (IsCriticalError(ErrorContext))
    {
        MicromegasTracing::Dispatch::FlushLogStream();
        MicromegasTracing::Dispatch::FlushMetricStream();
        MicromegasTracing::Dispatch::FlushCurrentThreadStream();
    }
}

// Assertion handler
void CheckGameState(bool bCondition, const FString& Message)
{
    if (!bCondition)
    {
        MICROMEGAS_LOG("Assert", MicromegasTracing::LogLevel::Fatal,
                       FString::Printf(TEXT("Assertion failed: %s"), *Message));

        // Flush before potential crash
        MicromegasTracing::Dispatch::FlushLogStream();
        MicromegasTracing::Dispatch::FlushMetricStream();
        MicromegasTracing::Dispatch::FlushCurrentThreadStream();

        check(false);
    }
}