2026-10-07 13:49:01 +00:00
|
|
|
// Portage UE 5.5 - lot debug. Voir LPDebugSubsystem.h.
|
|
|
|
|
|
|
|
|
|
#include "LPDebugSubsystem.h"
|
|
|
|
|
|
|
|
|
|
#include "LifePlay.h"
|
|
|
|
|
|
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
|
|
|
|
|
|
|
|
#include "Chaos/TriangleMeshImplicitObject.h"
|
2026-10-19 20:57:42 +00:00
|
|
|
#include "Components/CapsuleComponent.h"
|
2026-10-07 13:49:01 +00:00
|
|
|
#include "Components/PrimitiveComponent.h"
|
2026-10-19 20:57:42 +00:00
|
|
|
#include "Components/SkeletalMeshComponent.h"
|
|
|
|
|
#include "Animation/AnimInstance.h"
|
|
|
|
|
#include "Blueprint/UserWidget.h"
|
|
|
|
|
#include "GameFramework/InputSettings.h"
|
|
|
|
|
#include "GameFramework/PlayerController.h"
|
|
|
|
|
#include "GameFramework/PlayerInput.h"
|
|
|
|
|
#include "LPKeyboardSubsystem.h"
|
|
|
|
|
#include "UObject/UObjectIterator.h"
|
|
|
|
|
#include "Animation/AnimSingleNodeInstance.h"
|
|
|
|
|
#include "Containers/Ticker.h"
|
|
|
|
|
#include "Framework/Application/SlateApplication.h"
|
|
|
|
|
#include "GameFramework/Character.h"
|
|
|
|
|
#include "GameFramework/CharacterMovementComponent.h"
|
|
|
|
|
#include "Engine/GameInstance.h"
|
|
|
|
|
#include "UObject/UnrealType.h"
|
2026-10-07 13:49:01 +00:00
|
|
|
#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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-19 20:57:42 +00:00
|
|
|
// Lot STG (mesure M1) : position des personnages par rapport au sol visible (B-SOL) et distance entre eux (B-CHEV).
|
|
|
|
|
FAutoConsoleCommand* PersonsDumpCommand = nullptr;
|
|
|
|
|
FTSTicker::FDelegateHandle PersonsTickHandle;
|
|
|
|
|
constexpr double PersonsMinGapCm = 10.0;
|
|
|
|
|
|
|
|
|
|
FString ReadObjectName(const UObject* Container, FName Name)
|
|
|
|
|
{
|
|
|
|
|
const FObjectPropertyBase* Prop = Container ? CastField<FObjectPropertyBase>(Container->GetClass()->FindPropertyByName(Name)) : nullptr;
|
|
|
|
|
const UObject* Value = Prop ? Prop->GetObjectPropertyValue_InContainer(Container) : nullptr;
|
|
|
|
|
return Value ? Value->GetName() : TEXT("-");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FString ReadBool(const UObject* Container, FName Name)
|
|
|
|
|
{
|
|
|
|
|
const FBoolProperty* Prop = Container ? CastField<FBoolProperty>(Container->GetClass()->FindPropertyByName(Name)) : nullptr;
|
|
|
|
|
return Prop ? (Prop->GetPropertyValue_InContainer(Container) ? TEXT("1") : TEXT("0")) : TEXT("-");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Place d'un siege tiree du nom d'animation de meuble (fnt_xxx-<sexe>-<place>-<desc>) ; vide sinon.
|
|
|
|
|
FString SeatPlace(const ACharacter* Person)
|
|
|
|
|
{
|
|
|
|
|
TArray<FString> Parts;
|
|
|
|
|
ReadObjectName(Person, TEXT("currentAnimation")).ParseIntoArray(Parts, TEXT("-"));
|
|
|
|
|
return Parts.Num() >= 4 ? Parts[2] : FString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Une ligne par personnage (classe Person_C, joueur compris), puis les paires trop proches. Delta = sol visible - pieds :
|
|
|
|
|
// positif = enfonce. Ecrit au journal (LogLifePlay) et, si bFile, dans Saved/Profiling/LPDebug/PersonsDump_*.txt.
|
|
|
|
|
void WritePersonsDump(const TCHAR* Reason, bool bFile)
|
|
|
|
|
{
|
|
|
|
|
UWorld* World = FindPlayWorld();
|
|
|
|
|
TArray<FString> Lines;
|
|
|
|
|
Lines.Add(FString::Printf(TEXT("PersonsDump %s %s world=%s"), Reason, *FDateTime::Now().ToString(), World ? *World->GetName() : TEXT("(aucun)")));
|
|
|
|
|
if (!World)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogLifePlay, Display, TEXT("%s"), *Lines[0]);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (const UGameInstance* GI = World->GetGameInstance())
|
|
|
|
|
{
|
|
|
|
|
const FObjectPropertyBase* RoomProp = CastField<FObjectPropertyBase>(GI->GetClass()->FindPropertyByName(TEXT("Room")));
|
|
|
|
|
const AActor* Room = RoomProp ? Cast<AActor>(RoomProp->GetObjectPropertyValue_InContainer(GI)) : nullptr;
|
|
|
|
|
if (IsValid(Room) && Room->GetRootComponent())
|
|
|
|
|
{
|
|
|
|
|
const FVector RoomLoc = Room->GetActorLocation();
|
|
|
|
|
const FVector RoomScale = Room->GetRootComponent()->GetRelativeScale3D();
|
|
|
|
|
Lines.Add(FString::Printf(TEXT("Room loc=(%.1f,%.1f,%.1f) scale=(%.3f,%.3f,%.3f)"),
|
|
|
|
|
RoomLoc.X, RoomLoc.Y, RoomLoc.Z, RoomScale.X, RoomScale.Y, RoomScale.Z));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TArray<ACharacter*> Persons;
|
|
|
|
|
TArray<const AActor*> Ignored;
|
|
|
|
|
for (TActorIterator<ACharacter> It(World); It; ++It)
|
|
|
|
|
{
|
|
|
|
|
if (IsValid(*It) && It->GetClass()->GetName().StartsWith(TEXT("Person_C")))
|
|
|
|
|
{
|
|
|
|
|
Persons.Add(*It);
|
|
|
|
|
Ignored.Add(*It);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// M1c (STG-SF4b) : trace complexe = triangles du decor (pave), pas son volume de collision simple englobant.
|
|
|
|
|
FCollisionQueryParams Query(TEXT("LPPersonsDump"), true);
|
|
|
|
|
Query.AddIgnoredActors(Ignored);
|
|
|
|
|
|
|
|
|
|
Lines.Add(TEXT("actor;hidden;isMoving;occupyingFnt;x;y;z;capsuleBottomZ;feetZ;mode;floorHit;floorComp;floorZ;visibleComp;visibleZ;delta"));
|
|
|
|
|
for (const ACharacter* Person : Persons)
|
|
|
|
|
{
|
|
|
|
|
const FVector Loc = Person->GetActorLocation();
|
|
|
|
|
const UCapsuleComponent* Capsule = Person->GetCapsuleComponent();
|
|
|
|
|
const double CapsuleBottom = Loc.Z - (Capsule ? Capsule->GetScaledCapsuleHalfHeight() : 0.0);
|
|
|
|
|
const double Feet = Person->GetMesh() ? Person->GetMesh()->GetComponentLocation().Z : CapsuleBottom;
|
|
|
|
|
const UCharacterMovementComponent* Move = Person->GetCharacterMovement();
|
|
|
|
|
const FString Mode = Move ? StaticEnum<EMovementMode>()->GetNameStringByValue(static_cast<int64>(Move->MovementMode.GetValue())) : TEXT("-");
|
|
|
|
|
const bool bFloor = Move && Move->CurrentFloor.bBlockingHit;
|
|
|
|
|
const UPrimitiveComponent* FloorComp = bFloor ? Move->CurrentFloor.HitResult.GetComponent() : nullptr;
|
|
|
|
|
|
|
|
|
|
FHitResult Hit;
|
|
|
|
|
const bool bVisible = World->LineTraceSingleByChannel(Hit, Loc + FVector(0, 0, 100), Loc - FVector(0, 0, 300), ECC_Visibility, Query);
|
|
|
|
|
const FString VisibleComp = bVisible && Hit.GetComponent()
|
|
|
|
|
? FString::Printf(TEXT("%s.%s"), *GetNameSafe(Hit.GetActor()), *Hit.GetComponent()->GetName()) : TEXT("-");
|
|
|
|
|
|
|
|
|
|
Lines.Add(FString::Printf(TEXT("%s;%d;%s;%s;%.1f;%.1f;%.1f;%.1f;%.1f;%s;%d;%s;%.1f;%s;%s;%s"),
|
|
|
|
|
*Person->GetName(), Person->IsHidden() ? 1 : 0, *ReadBool(Person, TEXT("isMoving")), *ReadObjectName(Person, TEXT("OccupyingFnt")),
|
|
|
|
|
Loc.X, Loc.Y, Loc.Z, CapsuleBottom, Feet, *Mode, bFloor ? 1 : 0,
|
|
|
|
|
FloorComp ? *FString::Printf(TEXT("%s.%s"), *GetNameSafe(FloorComp->GetOwner()), *FloorComp->GetName()) : TEXT("-"),
|
|
|
|
|
bFloor ? Move->CurrentFloor.HitResult.ImpactPoint.Z : 0.0, *VisibleComp,
|
|
|
|
|
bVisible ? *FString::Printf(TEXT("%.1f"), Hit.ImpactPoint.Z) : TEXT("-"),
|
|
|
|
|
bVisible ? *FString::Printf(TEXT("%.1f"), Hit.ImpactPoint.Z - Feet) : TEXT("-")));
|
|
|
|
|
|
|
|
|
|
// M1b (B-DEPL) : etat qui peut bloquer la marche ou enfoncer le corps.
|
|
|
|
|
const USkeletalMeshComponent* Mesh = Person->GetMesh();
|
|
|
|
|
FString Anim = TEXT("-");
|
|
|
|
|
if (Mesh && Mesh->GetAnimationMode() == EAnimationMode::AnimationSingleNode)
|
|
|
|
|
{
|
|
|
|
|
const UAnimSingleNodeInstance* Single = Mesh->GetSingleNodeInstance();
|
|
|
|
|
Anim = FString::Printf(TEXT("single:%s"), *GetNameSafe(Single ? Single->GetAnimationAsset() : nullptr));
|
|
|
|
|
}
|
|
|
|
|
else if (Mesh && Mesh->GetAnimInstance())
|
|
|
|
|
{
|
|
|
|
|
Anim = FString::Printf(TEXT("bp:%s"), *Mesh->GetAnimInstance()->GetClass()->GetName());
|
|
|
|
|
}
|
|
|
|
|
Lines.Add(FString::Printf(TEXT("STATE %s coll=%s grav=%.2f moveTick=%d meshZ=%.1f anim=%s currentAnimation=%s"),
|
|
|
|
|
*Person->GetName(),
|
|
|
|
|
Capsule ? *StaticEnum<ECollisionEnabled::Type>()->GetNameStringByValue(static_cast<int64>(Capsule->GetCollisionEnabled())) : TEXT("-"),
|
|
|
|
|
Move ? Move->GravityScale : 0.f, Move && Move->IsComponentTickEnabled() ? 1 : 0,
|
|
|
|
|
Mesh ? Mesh->GetRelativeLocation().Z : 0.0, *Anim, *ReadObjectName(Person, TEXT("currentAnimation"))));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
const UGameInstance* GIState = World->GetGameInstance();
|
|
|
|
|
const FStrProperty* SceneProp = GIState ? CastField<FStrProperty>(GIState->GetClass()->FindPropertyByName(TEXT("CurrentScene"))) : nullptr;
|
|
|
|
|
const FString Scene = SceneProp ? SceneProp->GetPropertyValue_InContainer(GIState) : TEXT("-");
|
|
|
|
|
FString Focus = TEXT("-");
|
|
|
|
|
if (FSlateApplication::IsInitialized())
|
|
|
|
|
{
|
|
|
|
|
const TSharedPtr<SWidget> Focused = FSlateApplication::Get().GetKeyboardFocusedWidget();
|
|
|
|
|
Focus = Focused.IsValid() ? Focused->GetTypeAsString() : TEXT("aucun");
|
|
|
|
|
}
|
|
|
|
|
Lines.Add(FString::Printf(TEXT("GI CurrentScene=[%s] focus=%s"), *Scene, *Focus));
|
|
|
|
|
|
|
|
|
|
// Ce qui bloque ou nourrit la marche directe (memes tests que LPDirectControlSubsystem::MenuOnScreen et AxisValue).
|
|
|
|
|
const ULPKeyboardSubsystem* Keys = GIState ? GIState->GetSubsystem<ULPKeyboardSubsystem>() : nullptr;
|
|
|
|
|
static const TCHAR* MenuClasses[] = {
|
|
|
|
|
TEXT("Journal_C"), TEXT("MainMenu_C"), TEXT("CityMenu_C"), TEXT("StatMenu_C"),
|
|
|
|
|
TEXT("ContactMenu_C"), TEXT("Inventory_C"), TEXT("CharGen_C"), TEXT("ModManager_C"),
|
|
|
|
|
TEXT("BodyShapeMenu_C"), TEXT("Scene_C")
|
|
|
|
|
};
|
|
|
|
|
TArray<FString> Menus;
|
|
|
|
|
for (TObjectIterator<UUserWidget> It; It; ++It)
|
|
|
|
|
{
|
|
|
|
|
const UUserWidget* Widget = *It;
|
|
|
|
|
if (!Widget || Widget->HasAnyFlags(RF_ClassDefaultObject) || Widget->GetGameInstance() != GIState || !Widget->IsInViewport())
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const ESlateVisibility Visibility = Widget->GetVisibility();
|
|
|
|
|
if (Visibility == ESlateVisibility::Collapsed || Visibility == ESlateVisibility::Hidden)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const FString ClassName = Widget->GetClass()->GetName();
|
|
|
|
|
for (const TCHAR* Menu : MenuClasses)
|
|
|
|
|
{
|
|
|
|
|
if (ClassName == Menu)
|
|
|
|
|
{
|
|
|
|
|
Menus.Add(ClassName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const APlayerController* PC = World->GetFirstPlayerController();
|
|
|
|
|
const UInputSettings* Settings = UInputSettings::GetInputSettings();
|
|
|
|
|
double Forward = 0.0;
|
|
|
|
|
double Right = 0.0;
|
|
|
|
|
if (PC && PC->PlayerInput && Settings)
|
|
|
|
|
{
|
|
|
|
|
for (const FInputAxisKeyMapping& Map : Settings->GetAxisMappings())
|
|
|
|
|
{
|
|
|
|
|
const double Down = Map.Key.IsDigital() ? (PC->PlayerInput->IsPressed(Map.Key) ? 1.0 : 0.0) : PC->PlayerInput->GetKeyValue(Map.Key);
|
|
|
|
|
if (Map.AxisName == FName(TEXT("MoveForward")))
|
|
|
|
|
{
|
|
|
|
|
Forward += Down * Map.Scale;
|
|
|
|
|
}
|
|
|
|
|
else if (Map.AxisName == FName(TEXT("MoveRight")))
|
|
|
|
|
{
|
|
|
|
|
Right += Down * Map.Scale;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const FObjectPropertyBase* PlayerProp = GIState ? CastField<FObjectPropertyBase>(GIState->GetClass()->FindPropertyByName(TEXT("PlayerCharacter"))) : nullptr;
|
|
|
|
|
const ACharacter* Player = PlayerProp ? Cast<ACharacter>(PlayerProp->GetObjectPropertyValue_InContainer(GIState)) : nullptr;
|
|
|
|
|
const UCharacterMovementComponent* PlayerMove = Player ? Player->GetCharacterMovement() : nullptr;
|
|
|
|
|
const FBoolProperty* SexProp = GIState ? CastField<FBoolProperty>(GIState->GetClass()->FindPropertyByName(TEXT("SexOn"))) : nullptr;
|
|
|
|
|
const FBoolProperty* SceneOnProp = GIState ? CastField<FBoolProperty>(GIState->GetClass()->FindPropertyByName(TEXT("SceneOn"))) : nullptr;
|
|
|
|
|
Lines.Add(FString::Printf(TEXT("WALK keyMenu=%d menus=[%s] sexOn=%d sceneOn=%d forward=%.2f right=%.2f accel=%.1f speed=%.1f"),
|
|
|
|
|
Keys && Keys->IsMenuOpen() ? 1 : 0, *FString::Join(Menus, TEXT(",")),
|
|
|
|
|
SexProp ? (SexProp->GetPropertyValue_InContainer(GIState) ? 1 : 0) : -1,
|
|
|
|
|
SceneOnProp ? (SceneOnProp->GetPropertyValue_InContainer(GIState) ? 1 : 0) : -1, Forward, Right,
|
|
|
|
|
PlayerMove ? PlayerMove->GetCurrentAcceleration().Size() : -1.0, Player ? Player->GetVelocity().Size() : -1.0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// M1d (STG-C54) : en scene de sexe, pour chaque corps sans collision : racine du maillage, os le plus bas, bassin et surface
|
|
|
|
|
// sous le bassin (trace complexe, personnages ignores), animation et position dans le cycle.
|
|
|
|
|
{
|
|
|
|
|
const UGameInstance* CoupleGI = World->GetGameInstance();
|
|
|
|
|
const FBoolProperty* CoupleSexOn = CoupleGI ? CastField<FBoolProperty>(CoupleGI->GetClass()->FindPropertyByName(TEXT("SexOn"))) : nullptr;
|
|
|
|
|
if (CoupleSexOn && CoupleSexOn->GetPropertyValue_InContainer(CoupleGI))
|
|
|
|
|
{
|
|
|
|
|
for (const ACharacter* Person : Persons)
|
|
|
|
|
{
|
|
|
|
|
const UCapsuleComponent* Cap = Person->GetCapsuleComponent();
|
|
|
|
|
const USkeletalMeshComponent* Body = Person->GetMesh();
|
|
|
|
|
if (Person->IsHidden() || !Cap || !Body || Cap->GetCollisionEnabled() != ECollisionEnabled::NoCollision || Body->GetNumBones() == 0)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
double Lowest = TNumericLimits<double>::Max();
|
|
|
|
|
FName LowestBone;
|
|
|
|
|
for (int32 Bone = 0; Bone < Body->GetNumBones(); ++Bone)
|
|
|
|
|
{
|
|
|
|
|
const FName BoneName = Body->GetBoneName(Bone);
|
|
|
|
|
const double BoneZ = Body->GetBoneLocation(BoneName).Z;
|
|
|
|
|
if (BoneZ < Lowest)
|
|
|
|
|
{
|
|
|
|
|
Lowest = BoneZ;
|
|
|
|
|
LowestBone = BoneName;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
FName PelvisBone(TEXT("hip"));
|
|
|
|
|
if (Body->GetBoneIndex(PelvisBone) == INDEX_NONE)
|
|
|
|
|
{
|
|
|
|
|
PelvisBone = FName(TEXT("pelvis"));
|
|
|
|
|
}
|
|
|
|
|
if (Body->GetBoneIndex(PelvisBone) == INDEX_NONE)
|
|
|
|
|
{
|
|
|
|
|
PelvisBone = Body->GetBoneName(0);
|
|
|
|
|
}
|
|
|
|
|
const FVector Pelvis = Body->GetBoneLocation(PelvisBone);
|
|
|
|
|
FHitResult Under;
|
|
|
|
|
const bool bUnder = World->LineTraceSingleByChannel(Under, Pelvis + FVector(0.0, 0.0, 5.0), Pelvis - FVector(0.0, 0.0, 300.0), ECC_Visibility, Query);
|
|
|
|
|
// GetLength n'est pas const en UE 5.8 (AnimSingleNodeInstance.h) : pointeur non const.
|
|
|
|
|
UAnimSingleNodeInstance* Single = Body->GetSingleNodeInstance();
|
|
|
|
|
const UAnimationAsset* Playing = Single ? Single->GetAnimationAsset() : nullptr;
|
|
|
|
|
Lines.Add(FString::Printf(TEXT("COUPLE %s anim=%s t=%.2f/%.2f root=%.1f lowest=%.1f(%s) pelvis=%.1f(%s) under=%s:%.1f lowestMinusUnder=%.1f"),
|
|
|
|
|
*Person->GetName(), Playing ? *Playing->GetName() : TEXT("-"), Single ? Single->GetCurrentTime() : 0.0, Single ? Single->GetLength() : 0.0,
|
|
|
|
|
Body->GetComponentLocation().Z, Lowest, *LowestBone.ToString(), Pelvis.Z, *PelvisBone.ToString(),
|
|
|
|
|
bUnder && Under.GetComponent() ? *Under.GetComponent()->GetReadableName() : TEXT("-"), bUnder ? Under.ImpactPoint.Z : 0.0,
|
|
|
|
|
bUnder ? Lowest - Under.ImpactPoint.Z : 0.0));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Paires de personnages visibles : ecart entre capsules (distance 2D - rayons), seuil 10 cm.
|
|
|
|
|
double MinGap = TNumericLimits<double>::Max();
|
|
|
|
|
int32 Close = 0;
|
|
|
|
|
for (int32 i = 0; i < Persons.Num(); ++i)
|
|
|
|
|
{
|
|
|
|
|
for (int32 j = i + 1; j < Persons.Num(); ++j)
|
|
|
|
|
{
|
|
|
|
|
const ACharacter* A = Persons[i];
|
|
|
|
|
const ACharacter* B = Persons[j];
|
|
|
|
|
if (A->IsHidden() || B->IsHidden())
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
// Deux places distinctes d'un meme siege partagent le point d'acteur (decalage dans l'animation) : pas un chevauchement.
|
|
|
|
|
const FString FntA = ReadObjectName(A, TEXT("OccupyingFnt"));
|
|
|
|
|
if (FntA != TEXT("-") && FntA == ReadObjectName(B, TEXT("OccupyingFnt")) && SeatPlace(A) != SeatPlace(B))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const double Radii = (A->GetCapsuleComponent() ? A->GetCapsuleComponent()->GetScaledCapsuleRadius() : 0.0)
|
|
|
|
|
+ (B->GetCapsuleComponent() ? B->GetCapsuleComponent()->GetScaledCapsuleRadius() : 0.0);
|
|
|
|
|
const double Gap = FVector::Dist2D(A->GetActorLocation(), B->GetActorLocation()) - Radii;
|
|
|
|
|
MinGap = FMath::Min(MinGap, Gap);
|
|
|
|
|
if (Gap < PersonsMinGapCm)
|
|
|
|
|
{
|
|
|
|
|
++Close;
|
|
|
|
|
Lines.Add(FString::Printf(TEXT("CLOSE %s %s gap=%.1f dz=%.1f"), *A->GetName(), *B->GetName(), Gap,
|
|
|
|
|
A->GetActorLocation().Z - B->GetActorLocation().Z));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Lines.Add(FString::Printf(TEXT("Persons=%d Close=%d MinGap=%s"), Persons.Num(), Close,
|
|
|
|
|
MinGap < TNumericLimits<double>::Max() ? *FString::Printf(TEXT("%.1f"), MinGap) : TEXT("-")));
|
|
|
|
|
|
|
|
|
|
for (const FString& Line : Lines)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogLifePlay, Display, TEXT("LPDebug persons : %s"), *Line);
|
|
|
|
|
}
|
|
|
|
|
if (bFile)
|
|
|
|
|
{
|
|
|
|
|
const FString Stamp = FDateTime::Now().ToString(TEXT("%Y%m%d_%H%M%S"));
|
|
|
|
|
const FString Path = DebugDir() / FString::Printf(TEXT("PersonsDump_%s.txt"), *Stamp);
|
|
|
|
|
FFileHelper::SaveStringArrayToFile(Lines, *Path);
|
|
|
|
|
UE_LOG(LogLifePlay, Display, TEXT("LPDebug persons -> %s"), *Path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-07 13:49:01 +00:00
|
|
|
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"));
|
|
|
|
|
}
|
|
|
|
|
}));
|
2026-10-19 20:57:42 +00:00
|
|
|
PersonsDumpCommand = new FAutoConsoleCommand(
|
|
|
|
|
TEXT("LifePlay.PersonsDump"),
|
|
|
|
|
TEXT("Ecrit la position des personnages par rapport au sol visible et les paires a moins de 10 cm (lot STG)."),
|
|
|
|
|
FConsoleCommandDelegate::CreateLambda([]() { WritePersonsDump(TEXT("console"), true); }));
|
2026-10-07 13:49:01 +00:00
|
|
|
CommandOwner = this;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-19 20:57:42 +00:00
|
|
|
// -LPPersonsLog[=secondes] : releve periodique au journal, sans console (mesure M1 du lot STG).
|
|
|
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("LPPersonsLog")) || FCString::Strifind(FCommandLine::Get(), TEXT("-LPPersonsLog=")))
|
|
|
|
|
{
|
|
|
|
|
float PersonsSeconds = 5.f;
|
|
|
|
|
FParse::Value(FCommandLine::Get(), TEXT("LPPersonsLog="), PersonsSeconds);
|
|
|
|
|
PersonsSeconds = FMath::Max(PersonsSeconds, 1.f);
|
|
|
|
|
PersonsTickHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([](float)
|
|
|
|
|
{
|
|
|
|
|
WritePersonsDump(TEXT("periodique"), false);
|
|
|
|
|
return true;
|
|
|
|
|
}), PersonsSeconds);
|
|
|
|
|
UE_LOG(LogLifePlay, Display, TEXT("LPDebug persons : releve toutes les %.0f s"), PersonsSeconds);
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-07 13:49:01 +00:00
|
|
|
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);
|
|
|
|
|
}
|
2026-10-19 20:57:42 +00:00
|
|
|
if (PersonsTickHandle.IsValid())
|
|
|
|
|
{
|
|
|
|
|
FTSTicker::GetCoreTicker().RemoveTicker(PersonsTickHandle);
|
|
|
|
|
PersonsTickHandle.Reset();
|
|
|
|
|
}
|
2026-10-07 13:49:01 +00:00
|
|
|
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
|