// 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/CapsuleComponent.h" #include "Components/PrimitiveComponent.h" #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" #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 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 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 ByClass; TMap TrisByClass; TArray 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(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 Bodies; for (TActorIterator It(World); It; ++It) { const FString ClassName = It->GetClass()->GetName(); TInlineComponentArray 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 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> Classes; for (const TPair& Pair : Tally.ByClass) { Classes.Add(Pair); } Classes.Sort([](const TPair& A, const TPair& 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; } // 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(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(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---) ; vide sinon. FString SeatPlace(const ACharacter* Person) { TArray 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 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(GI->GetClass()->FindPropertyByName(TEXT("Room"))); const AActor* Room = RoomProp ? Cast(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 Persons; TArray Ignored; for (TActorIterator 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()->GetNameStringByValue(static_cast(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()->GetNameStringByValue(static_cast(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(GIState->GetClass()->FindPropertyByName(TEXT("CurrentScene"))) : nullptr; const FString Scene = SceneProp ? SceneProp->GetPropertyValue_InContainer(GIState) : TEXT("-"); FString Focus = TEXT("-"); if (FSlateApplication::IsInitialized()) { const TSharedPtr 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() : 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 Menus; for (TObjectIterator 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(GIState->GetClass()->FindPropertyByName(TEXT("PlayerCharacter"))) : nullptr; const ACharacter* Player = PlayerProp ? Cast(PlayerProp->GetObjectPropertyValue_InContainer(GIState)) : nullptr; const UCharacterMovementComponent* PlayerMove = Player ? Player->GetCharacterMovement() : nullptr; const FBoolProperty* SexProp = GIState ? CastField(GIState->GetClass()->FindPropertyByName(TEXT("SexOn"))) : nullptr; const FBoolProperty* SceneOnProp = GIState ? CastField(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(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::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::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::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); } } void WriteBookmark(const TArray& 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")); } })); 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); })); CommandOwner = this; } // -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); } 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 (PersonsTickHandle.IsValid()) { FTSTicker::GetCoreTicker().RemoveTicker(PersonsTickHandle); PersonsTickHandle.Reset(); } 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