LifePlay-Revival/Source/LifePlay/LPDebugSubsystem.cpp

502 lines
16 KiB
C++
Raw Normal View History

// Portage UE 5.5 - lot debug. Voir LPDebugSubsystem.h.
#include "LPDebugSubsystem.h"
#include "LifePlay.h"
#if !UE_BUILD_SHIPPING
#include "Chaos/TriangleMeshImplicitObject.h"
#include "Components/PrimitiveComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Engine/Engine.h"
#include "Engine/World.h"
#include "EngineUtils.h"
#include "HAL/FileManager.h"
#include "HAL/IConsoleManager.h"
#include "HAL/PlatformMemory.h"
#include "Misc/CommandLine.h"
#include "Misc/CoreDelegates.h"
#include "Misc/FileHelper.h"
#include "Misc/Parse.h"
#include "Misc/Paths.h"
#include "PhysicsEngine/BodySetup.h"
#include "ProfilingDebugging/MiscTrace.h"
namespace
{
FAutoConsoleCommand* MemSnapshotCommand = nullptr;
FAutoConsoleCommand* CollisionDumpCommand = nullptr;
FAutoConsoleCommand* BookmarkCommand = nullptr;
FAutoConsoleCommand* HitchSummaryCommand = nullptr;
ULPDebugSubsystem* CommandOwner = nullptr;
ULPDebugSubsystem* FindDebugSubsystem()
{
return CommandOwner;
}
UWorld* FindPlayWorld()
{
if (!GEngine)
{
return nullptr;
}
if (UWorld* Play = GEngine->GetCurrentPlayWorld())
{
return Play;
}
for (const FWorldContext& Context : GEngine->GetWorldContexts())
{
UWorld* World = Context.World();
if (World && (World->WorldType == EWorldType::Game || World->WorldType == EWorldType::PIE))
{
return World;
}
}
return nullptr;
}
FString DebugDir()
{
const FString Dir = FPaths::ProjectSavedDir() / TEXT("Profiling/LPDebug");
IFileManager::Get().MakeDirectory(*Dir, true);
return Dir;
}
void WriteMemSnapshot()
{
const FString Stamp = FDateTime::Now().ToString(TEXT("%Y%m%d_%H%M%S"));
const FString Path = DebugDir() / FString::Printf(TEXT("MemSnapshot_%s.txt"), *Stamp);
const FPlatformMemoryStats Stats = FPlatformMemory::GetStats();
TArray<FString> Lines;
Lines.Add(FString::Printf(TEXT("When=%s"), *FDateTime::Now().ToString()));
Lines.Add(FString::Printf(TEXT("UsedPhysicalMB=%.1f"), Stats.UsedPhysical / (1024.0 * 1024.0)));
Lines.Add(FString::Printf(TEXT("PeakUsedPhysicalMB=%.1f"), Stats.PeakUsedPhysical / (1024.0 * 1024.0)));
Lines.Add(FString::Printf(TEXT("AvailablePhysicalMB=%.1f"), Stats.AvailablePhysical / (1024.0 * 1024.0)));
Lines.Add(FString::Printf(TEXT("UsedVirtualMB=%.1f"), Stats.UsedVirtual / (1024.0 * 1024.0)));
UWorld* World = FindPlayWorld();
if (GEngine)
{
const bool bReport = GEngine->Exec(World, TEXT("memreport"));
Lines.Add(FString::Printf(TEXT("memreport=%s"), bReport ? TEXT("ok") : TEXT("echec")));
}
const FString ReportDir = FPaths::ProjectSavedDir() / TEXT("Profiling/MemReports");
TArray<FString> Reports;
IFileManager::Get().FindFilesRecursive(Reports, *ReportDir, TEXT("*.memreport"), true, false);
if (Reports.Num() > 0)
{
Reports.Sort();
Lines.Add(FString::Printf(TEXT("NewestMemReport=%s"), *Reports.Last()));
}
Lines.Add(TEXT("PhysicsMB=voir LLM (colonne Physics) ou le MemReport ; ce fichier ne remplace pas la session B"));
FFileHelper::SaveStringArrayToFile(Lines, *Path);
UE_LOG(LogLifePlay, Display, TEXT("LPDebug MemSnapshot : %s"), *Path);
}
struct FCollisionOwner
{
FString Actor;
FString ClassName;
FString Component;
FString Profile;
int64 Triangles = 0;
int32 TriMeshes = 0;
int32 Simple = 0;
int32 Capsules = 0;
};
struct FCollisionTally
{
FString WorldName;
int32 Prim = 0;
int32 Simulating = 0;
int32 QueryOnly = 0;
int32 Persons = 0;
int32 Players = 0;
int32 Street = 0;
int32 UniqueBodies = 0;
int32 UniqueSimple = 0;
int32 UniqueTriMeshes = 0;
int32 UniqueCapsules = 0;
int64 Triangles = 0;
TMap<FString, int32> ByClass;
TMap<FString, int64> TrisByClass;
TArray<FCollisionOwner> Owners;
};
int64 CountTriangles(const UBodySetup* Body)
{
int64 Count = 0;
if (!Body)
{
return 0;
}
for (const Chaos::FTriangleMeshImplicitObjectPtr& Mesh : Body->TriMeshGeometries)
{
if (Mesh)
{
Count += Mesh->Elements().GetNumTriangles();
}
}
return Count;
}
FString ComponentLabel(const UPrimitiveComponent* Prim)
{
if (const UStaticMeshComponent* MeshComp = Cast<UStaticMeshComponent>(Prim))
{
if (const UStaticMesh* Mesh = MeshComp->GetStaticMesh())
{
return Mesh->GetName();
}
}
return Prim ? Prim->GetName() : FString();
}
FCollisionTally GatherCollision()
{
FCollisionTally Tally;
UWorld* World = FindPlayWorld();
Tally.WorldName = World ? World->GetName() : TEXT("(aucune)");
if (!World)
{
return Tally;
}
TSet<const UBodySetup*> Bodies;
for (TActorIterator<AActor> It(World); It; ++It)
{
const FString ClassName = It->GetClass()->GetName();
TInlineComponentArray<UPrimitiveComponent*> Comps;
It->GetComponents(Comps);
for (UPrimitiveComponent* Prim : Comps)
{
if (!Prim || !Prim->IsCollisionEnabled())
{
continue;
}
++Tally.Prim;
Tally.ByClass.FindOrAdd(ClassName)++;
if (ClassName.StartsWith(TEXT("Person_C")))
{
++Tally.Persons;
}
else if (ClassName.StartsWith(TEXT("PC_C")))
{
++Tally.Players;
}
else if (ClassName.Contains(TEXT("StreetMap")))
{
++Tally.Street;
}
if (Prim->IsSimulatingPhysics())
{
++Tally.Simulating;
}
if (Prim->GetCollisionEnabled() == ECollisionEnabled::QueryOnly)
{
++Tally.QueryOnly;
}
UBodySetup* Body = Prim->GetBodySetup();
if (!Body || Bodies.Contains(Body))
{
continue;
}
Bodies.Add(Body);
const int64 Tris = CountTriangles(Body);
const int32 Capsules = Body->AggGeom.SphylElems.Num();
const int32 Simple = Body->AggGeom.GetElementCount();
Tally.UniqueBodies++;
Tally.UniqueSimple += Simple;
Tally.UniqueCapsules += Capsules;
Tally.UniqueTriMeshes += Body->TriMeshGeometries.Num();
Tally.Triangles += Tris;
Tally.TrisByClass.FindOrAdd(ClassName) += Tris;
if (Tris > 0 || Simple > 0)
{
FCollisionOwner& Owner = Tally.Owners.AddDefaulted_GetRef();
Owner.Actor = It->GetName();
Owner.ClassName = ClassName;
Owner.Component = ComponentLabel(Prim);
Owner.Profile = Prim->GetCollisionProfileName().ToString();
Owner.Triangles = Tris;
Owner.TriMeshes = Body->TriMeshGeometries.Num();
Owner.Simple = Simple;
Owner.Capsules = Capsules;
}
}
}
Tally.Owners.Sort([](const FCollisionOwner& A, const FCollisionOwner& B)
{
return A.Triangles > B.Triangles;
});
return Tally;
}
FString WriteCollisionDump(const TCHAR* Reason, const FCollisionTally* Existing = nullptr)
{
const FCollisionTally Tally = Existing ? *Existing : GatherCollision();
const FString Stamp = FDateTime::Now().ToString(TEXT("%Y%m%d_%H%M%S"));
const FString Path = DebugDir() / FString::Printf(TEXT("CollisionDump_%s.txt"), *Stamp);
TArray<FString> Lines;
Lines.Add(FString::Printf(TEXT("When=%s"), *FDateTime::Now().ToString()));
Lines.Add(FString::Printf(TEXT("Reason=%s"), Reason));
Lines.Add(FString::Printf(TEXT("World=%s"), *Tally.WorldName));
Lines.Add(FString::Printf(TEXT("CollisionPrimitives=%d"), Tally.Prim));
Lines.Add(FString::Printf(TEXT("PersonPrimitives=%d"), Tally.Persons));
Lines.Add(FString::Printf(TEXT("PlayerPrimitives=%d"), Tally.Players));
Lines.Add(FString::Printf(TEXT("StreetMapPrimitives=%d"), Tally.Street));
Lines.Add(FString::Printf(TEXT("Simulating=%d"), Tally.Simulating));
Lines.Add(FString::Printf(TEXT("QueryOnly=%d"), Tally.QueryOnly));
Lines.Add(FString::Printf(TEXT("UniqueBodySetups=%d"), Tally.UniqueBodies));
Lines.Add(FString::Printf(TEXT("UniqueSimpleShapes=%d"), Tally.UniqueSimple));
Lines.Add(FString::Printf(TEXT("UniqueCapsules=%d"), Tally.UniqueCapsules));
Lines.Add(FString::Printf(TEXT("UniqueTriMeshes=%d"), Tally.UniqueTriMeshes));
Lines.Add(FString::Printf(TEXT("Triangles=%lld"), Tally.Triangles));
Lines.Add(TEXT("TopClasses:"));
TArray<TPair<FString, int32>> Classes;
for (const TPair<FString, int32>& Pair : Tally.ByClass)
{
Classes.Add(Pair);
}
Classes.Sort([](const TPair<FString, int32>& A, const TPair<FString, int32>& B) { return A.Value > B.Value; });
for (int32 i = 0; i < Classes.Num() && i < 15; ++i)
{
const int64 Tris = Tally.TrisByClass.FindRef(Classes[i].Key);
Lines.Add(FString::Printf(TEXT(" %d %s triangles=%lld"), Classes[i].Value, *Classes[i].Key, Tris));
}
Lines.Add(TEXT("OwnersByTriangles:"));
for (int32 i = 0; i < Tally.Owners.Num() && i < 12; ++i)
{
const FCollisionOwner& Owner = Tally.Owners[i];
Lines.Add(FString::Printf(
TEXT(" triangles=%lld trimesh=%d simple=%d capsules=%d profile=%s %s %s %s"),
Owner.Triangles, Owner.TriMeshes, Owner.Simple, Owner.Capsules,
*Owner.Profile, *Owner.ClassName, *Owner.Actor, *Owner.Component));
}
if (Tally.WorldName == TEXT("(aucune)"))
{
Lines.Add(TEXT("Note=monde de jeu absent (appeler la commande une fois la partie chargee)"));
}
Lines.Add(TEXT("Switch=-LPCollisionLog pour echantillonner apres le chargement, pas au ExecCmds de demarrage"));
FFileHelper::SaveStringArrayToFile(Lines, *Path);
UE_LOG(LogLifePlay, Display, TEXT("LPDebug collision : %d primitives, %d trimesh, %lld triangles (%s) -> %s"),
Tally.Prim, Tally.UniqueTriMeshes, Tally.Triangles, Reason, *Path);
TRACE_BOOKMARK(TEXT("LP Collision %lld tris"), Tally.Triangles);
return Path;
}
void WriteBookmark(const TArray<FString>& Args)
{
const FString Text = Args.Num() > 0 ? FString::Join(Args, TEXT(" ")) : TEXT("manuel");
UE_LOG(LogLifePlay, Display, TEXT("LPDebug bookmark : %s"), *Text);
TRACE_BOOKMARK(TEXT("LP %s"), *Text);
}
}
void ULPDebugSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
if (!MemSnapshotCommand)
{
MemSnapshotCommand = new FAutoConsoleCommand(
TEXT("LifePlay.MemSnapshot"),
TEXT("Ecrit Saved/Profiling/LPDebug/MemSnapshot_*.txt et lance memreport."),
FConsoleCommandDelegate::CreateLambda([]() { WriteMemSnapshot(); }));
CollisionDumpCommand = new FAutoConsoleCommand(
TEXT("LifePlay.CollisionDump"),
TEXT("Ecrit un releve des primitives de collision du monde courant."),
FConsoleCommandDelegate::CreateLambda([]() { WriteCollisionDump(TEXT("console")); }));
BookmarkCommand = new FAutoConsoleCommand(
TEXT("LifePlay.Bookmark"),
TEXT("Signet Insights (TRACE_BOOKMARK) et ligne LogLifePlay. Argument : texte du signet."),
FConsoleCommandWithArgsDelegate::CreateStatic(&WriteBookmark));
HitchSummaryCommand = new FAutoConsoleCommand(
TEXT("LifePlay.HitchSummary"),
TEXT("Ecrit le bilan courant des a-coups (-LPHitchLog)."),
FConsoleCommandDelegate::CreateLambda([]()
{
if (ULPDebugSubsystem* Sub = FindDebugSubsystem())
{
Sub->LogHitchSummary(TEXT("console"));
}
else
{
UE_LOG(LogLifePlay, Display, TEXT("LPDebug hitch : sous-systeme absent"));
}
}));
CommandOwner = this;
}
bHitchLog = FParse::Param(FCommandLine::Get(), TEXT("LPHitchLog"));
HitchMs = 50.f;
FParse::Value(FCommandLine::Get(), TEXT("LPHitchMs="), HitchMs);
bCollisionLog = FParse::Param(FCommandLine::Get(), TEXT("LPCollisionLog"));
if (bHitchLog || bCollisionLog)
{
StartSeconds = FPlatformTime::Seconds();
LastFrameSeconds = StartSeconds;
}
if (bHitchLog)
{
UE_LOG(LogLifePlay, Display, TEXT("LPDebug hitch : actif, seuil %.1f ms"), HitchMs);
}
else
{
UE_LOG(LogLifePlay, Display, TEXT("LPDebug hitch : inactif"));
}
if (bCollisionLog)
{
const FString Stamp = FDateTime::Now().ToString(TEXT("%Y%m%d_%H%M%S"));
CollisionTrackPath = DebugDir() / FString::Printf(TEXT("CollisionTrack_%s.txt"), *Stamp);
FFileHelper::SaveStringToFile(
TEXT("t\tworld\tprim\tpersons\tplayers\tstreet\ttrimesh\ttriangles\n"),
*CollisionTrackPath);
NextCollisionSeconds = FPlatformTime::Seconds() + 2.0;
UE_LOG(LogLifePlay, Display, TEXT("LPDebug collision : actif, premier echantillon dans 2 s -> %s"), *CollisionTrackPath);
}
else
{
UE_LOG(LogLifePlay, Display, TEXT("LPDebug collision : inactif"));
}
if (bHitchLog || bCollisionLog)
{
FCoreDelegates::OnEndFrame.AddUObject(this, &ULPDebugSubsystem::OnEndFrame);
}
}
void ULPDebugSubsystem::Deinitialize()
{
if (bHitchLog)
{
LogHitchSummary(TEXT("fin"));
}
if (bCollisionLog)
{
LogCollisionSummary(TEXT("fin"));
}
if (bHitchLog || bCollisionLog)
{
FCoreDelegates::OnEndFrame.RemoveAll(this);
}
if (CommandOwner == this)
{
CommandOwner = nullptr;
}
Super::Deinitialize();
}
void ULPDebugSubsystem::OnEndFrame()
{
const double Now = FPlatformTime::Seconds();
if (bCollisionLog && Now >= NextCollisionSeconds)
{
NextCollisionSeconds = Now + 2.0;
SampleCollision();
}
if (!bHitchLog)
{
return;
}
const double DeltaMs = (Now - LastFrameSeconds) * 1000.0;
LastFrameSeconds = Now;
if (DeltaMs >= HitchMs)
{
++HitchCount;
MaxHitchMs = FMath::Max(MaxHitchMs, DeltaMs);
if (HitchLines < 100 || (HitchCount % 25) == 0)
{
++HitchLines;
UE_LOG(LogLifePlay, Warning, TEXT("LPDebug hitch : %.1f ms (seuil %.1f) #%d"), DeltaMs, HitchMs, HitchCount);
TRACE_BOOKMARK(TEXT("LP Hitch %.1f ms"), DeltaMs);
}
}
if (!bLoggedTwoMinuteSummary && (Now - StartSeconds) >= 120.0)
{
bLoggedTwoMinuteSummary = true;
LogHitchSummary(TEXT("2 min"));
}
}
void ULPDebugSubsystem::LogHitchSummary(const TCHAR* When) const
{
const double Elapsed = StartSeconds > 0.0 ? FPlatformTime::Seconds() - StartSeconds : 0.0;
UE_LOG(LogLifePlay, Display, TEXT("LPDebug hitch : %d hitch(s) en %.1f s (seuil %.1f ms), max %.1f ms, %s"),
HitchCount, Elapsed, HitchMs, MaxHitchMs, When);
}
void ULPDebugSubsystem::SampleCollision()
{
const FCollisionTally Tally = GatherCollision();
++CollisionSamples;
PeakPrim = FMath::Max(PeakPrim, Tally.Prim);
PeakTriangles = FMath::Max(PeakTriangles, Tally.Triangles);
const double Elapsed = StartSeconds > 0.0 ? FPlatformTime::Seconds() - StartSeconds : FPlatformTime::Seconds();
const FString Line = FString::Printf(
TEXT("%.1f\t%s\t%d\t%d\t%d\t%d\t%d\t%lld\n"),
Elapsed, *Tally.WorldName, Tally.Prim, Tally.Persons, Tally.Players, Tally.Street,
Tally.UniqueTriMeshes, Tally.Triangles);
FFileHelper::SaveStringToFile(Line, *CollisionTrackPath, FFileHelper::EEncodingOptions::AutoDetect, &IFileManager::Get(), FILEWRITE_Append);
UE_LOG(LogLifePlay, Display, TEXT("LPDebug collision : %.0f s %s prim=%d pnj=%d trimesh=%d triangles=%lld"),
Elapsed, *Tally.WorldName, Tally.Prim, Tally.Persons, Tally.UniqueTriMeshes, Tally.Triangles);
const bool bGrew = Tally.Triangles >= LastDumpedTriangles + 1000 || Tally.UniqueTriMeshes > LastDumpedTriMeshes;
if (CollisionDumps < 4 && Tally.Triangles > 0 && bGrew)
{
WriteCollisionDump(TEXT("suivi"), &Tally);
++CollisionDumps;
LastDumpedTriangles = Tally.Triangles;
LastDumpedTriMeshes = Tally.UniqueTriMeshes;
}
}
void ULPDebugSubsystem::LogCollisionSummary(const TCHAR* When) const
{
if (CollisionDumps == 0 && PeakPrim > 0)
{
WriteCollisionDump(TEXT("fin"));
}
UE_LOG(LogLifePlay, Display, TEXT("LPDebug collision : %d echantillon(s), %d dump(s), max %d primitives, %lld triangles, %s"),
CollisionSamples, CollisionDumps, PeakPrim, PeakTriangles, When);
}
#else
void ULPDebugSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
}
void ULPDebugSubsystem::Deinitialize()
{
Super::Deinitialize();
}
void ULPDebugSubsystem::OnEndFrame()
{
}
void ULPDebugSubsystem::LogHitchSummary(const TCHAR* When) const
{
(void)When;
}
void ULPDebugSubsystem::SampleCollision()
{
}
void ULPDebugSubsystem::LogCollisionSummary(const TCHAR* When) const
{
(void)When;
}
#endif