Dbg : outillage de debug (BuildDebug, LPDebugSubsystem, lanceurs, événements LoadTxt / LPDataCache), documents 18 et 19
Phase Dbg (CHG-114, 117, 118). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
14768a96a8
commit
b8414af456
20 changed files with 1708 additions and 8 deletions
|
|
@ -98,10 +98,10 @@ $Proj = 'D:\Game\LifePlay-master 5.5\LifePlay.uproject'
|
|||
-archivedirectory=D:\Game\LifePlay_Build_5.5_<nom> -unattended -utf8output -nop4
|
||||
```
|
||||
|
||||
- **Pas de `-pak`** : comme la version de l'auteur (`UsePakFile=False`), le jeu est livré en fichiers
|
||||
nus ; `Modules` doit rester modifiable par les joueurs et les mods.
|
||||
- Depuis le lot O2 le contenu est en conteneurs IoStore (`UsePakFile`, `bUseIoStore`, `bCompressed`
|
||||
dans `DefaultGame.ini`). `Modules` reste en fichiers nus.
|
||||
- `-clientconfig=Shipping` pour une version distribuable (ne journalise pas) ; `Development` pour
|
||||
toute mesure.
|
||||
toute mesure. Builds de plongée : `Tools\BuildDebug.ps1` (`18_Build_Debug.md`), jamais `-nodebuginfo`.
|
||||
- Contrôle T-PKG-01 : ≥ 8 555 `.uasset` sous `Windows/LifePlay/Content` (8 561 attendus).
|
||||
- Le résultat est dans `<archivedirectory>\Windows\LifePlay.exe`.
|
||||
|
||||
|
|
@ -218,8 +218,11 @@ void UMonSubsystem::Deinitialize() { FTSTicker::GetCoreTicker().RemoveTicker(Tic
|
|||
4. Pour un état d'exécution (positions, variables), utiliser les options `-LPDump*` et `-LPTrace*`
|
||||
(`09_Commandes_Utiles.md` §4) dans un fichier `.bat` placé à côté de l'exécutable, que
|
||||
l'utilisateur lance à la place de `LifePlay.exe`.
|
||||
5. Pour un graphe : `LPDumpFunction` sur les deux arbres, puis diff.
|
||||
6. Pour un écart de comportement du moteur : lire les sources moteur 4.27 et 5.5 sur les cas limites ;
|
||||
5. Pour une mesure de performance (à-coups, mémoire, non-régression) : build empaquetée via
|
||||
`Tools\BuildDebug.ps1` et sessions A/B/C (`18_Build_Debug.md`). Pas `UnrealEditor.exe -game`
|
||||
pour des millisecondes comparables à O0.
|
||||
6. Pour un graphe : `LPDumpFunction` sur les deux arbres, puis diff.
|
||||
7. Pour un écart de comportement du moteur : lire les sources moteur 4.27 et 5.5 sur les cas limites ;
|
||||
en faire un test (`LPTestStringCompat`).
|
||||
|
||||
Inspection en direct : avec l'éditeur ouvert et le serveur MCP lancé (`McpAutomationBridge`, port 3000),
|
||||
|
|
|
|||
289
Docs/Migration/18_Build_Debug.md
Normal file
289
Docs/Migration/18_Build_Debug.md
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
# 18 — Build debug (mesure, pas optimisation)
|
||||
|
||||
Capacité de mesure reproductible après l'audit O0. Le Shipping joueur ne change pas
|
||||
(`IncludeDebugFiles=False` dans `Config/DefaultGame.ini`, aucun coût runtime : le sous-système
|
||||
est compilé vide en Shipping). Skill agent : `.cursor/skills/lifeplay-debug-build/`.
|
||||
Plan de pilotage du lot : [`19_Plan_Outillage_Debug.md`](19_Plan_Outillage_Debug.md).
|
||||
|
||||
## 1. Deux builds
|
||||
|
||||
| Config | Rôle | Comparer les ms absolues |
|
||||
|---|---|---|
|
||||
| **Development** | Baseline chiffrée, session C, non-régression vs O0 | Oui |
|
||||
| **DebugGame** | Plongée (checks, PDB, sessions A et B) | Non — ratios, signatures, piles |
|
||||
| **Shipping** | Joueurs | Hors sujet |
|
||||
|
||||
Ne pas reconstruire DebugGame à chaque itération. Itérer dans l'éditeur ou en Development ;
|
||||
reconstruire DebugGame seulement quand les checks, les PDB ou un outil externe l'exigent.
|
||||
|
||||
## 2. Empaqueter
|
||||
|
||||
```powershell
|
||||
.\Tools\BuildDebug.ps1 [-Config DebugGame|Development] [-ArchiveDir <chemin>] [-UE <moteur>] [-SkipBuild]
|
||||
```
|
||||
|
||||
- Config par défaut : `DebugGame`.
|
||||
- Archives : `D:\Game\LifePlay_Build_5.5_DebugGame` ou `D:\Game\LifePlay_Build_5.5_Development`.
|
||||
- Jamais `-nodebuginfo`. Le PDB `Binaries\Win64\*Win64-<Config>.pdb` est recopié dans
|
||||
`Windows\LifePlay\Binaries\Win64` parce que `IncludeDebugFiles` reste `False` (le Shipping
|
||||
lancé depuis l'éditeur ne doit pas embarquer les PDB).
|
||||
- Cuisson alignée sur le projet actuel (lot O2) : `-pak -iostore -compressed`. `Modules` reste en
|
||||
fichiers nus via `DefaultGame.ini`.
|
||||
- Après l'archive, les lanceurs `Tools/debug/*.bat` sont copiés à côté de `Windows\LifePlay.exe`.
|
||||
|
||||
Journal UAT : `Saved\Logs\portage\BuildDebug_<config>_<horodatage>.log`.
|
||||
|
||||
## 3. Lanceurs
|
||||
|
||||
Scénario unique (O0 §5, environ 15 min) : partie avancée → carte de la ville → intérieur peuplé →
|
||||
extérieur peuplé → scène à plusieurs personnages → quitter normalement.
|
||||
|
||||
| Session | Build | Lanceur | But |
|
||||
|---|---|---|---|
|
||||
| A | DebugGame | `LifePlay_debug_sessionA_insights.bat` | À-coups / travail de `GI` (G6) |
|
||||
| B | DebugGame | `LifePlay_debug_sessionB_mem.bat` | Fuites, physique, collisions |
|
||||
| C | Development | `LifePlay_debug_sessionC_profil.bat` | Non-régression CSV + LLM |
|
||||
|
||||
Auxiliaires, hors chemin critique : `LifePlay_debug_bugs.bat`, `LifePlay_debug_gpu.bat` (PIX **ou**
|
||||
RGP, jamais les deux), `LifePlay_debug_collision.bat`. `LifePlay_debug_ui.bat` ouvre une
|
||||
fenêtre pour cocher ces options (le canal `memory` reste décoché ; `-csvCaptureFrames` n'est
|
||||
pas proposé). Le `.ps1` du même nom doit rester à côté du `.bat`.
|
||||
|
||||
Flags des sessions :
|
||||
|
||||
| Lanceur | Ligne de commande |
|
||||
|---|---|
|
||||
| A | `-trace=cpu,frame,bookmark,loadtime,memory -statnamedevents -tracefile=<Saved>/Profiling/SessionA.utrace -LPHitchLog` |
|
||||
| B | `-llm -llmcsv -LPCollisionLog -ExecCmds="LifePlay.MemSnapshot, p.Chaos.StartVDRecording"` |
|
||||
| C | `-csvGpuStats -llm -llmcsv -ExecCmds="csvprofile repeat=20, csvprofile frames=9000"` |
|
||||
| bugs | `-LogCmds="LogModdingExtra Verbose"` |
|
||||
| gpu | `-d3d12` (PIX). RGP : attacher au processus, sans ce lanceur |
|
||||
| collision | `-LPCollisionLog -ExecCmds="p.Chaos.StartVDRecording, show collision"` |
|
||||
| ui | fenêtre : les mêmes options, une par une |
|
||||
|
||||
**Piège CSV.** Ne jamais passer `-csvCaptureFrames`. La capture démarre avant l'initialisation du
|
||||
RHI et le moteur s'arrête (`IsRayTracingAllowed() may only be called once RHI is initialized`).
|
||||
La session C démarre le CSV par `-ExecCmds`, après le rendu. Mesurer un jeu empaqueté, pas
|
||||
`UnrealEditor.exe -game`, pour les chiffres comparables à O0.
|
||||
|
||||
## 4. Collecte
|
||||
|
||||
```powershell
|
||||
.\Tools\CollectDebugArtifacts.ps1 -Lot <lot> -BuildDir <archive> -Session A|B|C|bugs|gpu|collision
|
||||
```
|
||||
|
||||
Destination : `D:\Game\LifePlay_Backup_5.5\<lot>\Session<session>\` (journaux, CSV, LLM, `.utrace`,
|
||||
MemReport, `Profiling\LPDebug`, enregistrements ChaosVD). Le script échoue si rien n'est copié.
|
||||
|
||||
Résumé CSV (session C) :
|
||||
|
||||
```powershell
|
||||
.\Tools\SummarizeDebugCsv.ps1 -Csv <dossier ou fichier CSV>
|
||||
```
|
||||
|
||||
Médiane `FrameTime`, images > 50 ms et > 100 ms. Référence O0 (build L7cdj, 36 004 images) :
|
||||
médiane 16,7 ms ; 159 images > 50 ms ; 112 images > 100 ms ; physique Chaos environ 463 Mo.
|
||||
|
||||
## 5. Runtime non-Shipping
|
||||
|
||||
`ULPDebugSubsystem` (`UGameInstanceSubsystem`), derrière `#if !UE_BUILD_SHIPPING`.
|
||||
|
||||
| Entrée | Effet |
|
||||
|---|---|
|
||||
| `-LPHitchLog` | Journal des images ≥ seuil. Sans ce flag, aucun relevé d'image |
|
||||
| `-LPHitchMs=N` | Seuil en millisecondes (50 par défaut) |
|
||||
| `-LPCollisionLog` | Échantillon toutes les 2 s, dump dès qu'un trimesh a des triangles, avec l'acteur qui le porte. Inactif sans ce flag |
|
||||
| `LifePlay.Bookmark <texte>` | `TRACE_BOOKMARK` et ligne `LPDebug bookmark :` |
|
||||
| `LifePlay.MemSnapshot` | `Saved/Profiling/LPDebug/MemSnapshot_*.txt` + `memreport` |
|
||||
| `LifePlay.CollisionDump` | `CollisionDump_*.txt` immédiat (console). Le suivi automatique est `-LPCollisionLog`, pas cette commande au démarrage |
|
||||
| `LifePlay.HitchSummary` | Bilan courant (`LPDebug hitch : N hitch(s) en …`) |
|
||||
|
||||
Un bilan est aussi écrit à 2 minutes et à la fermeture quand `-LPHitchLog` est actif.
|
||||
Ligne de hitch : `LPDebug hitch : <ms> ms (seuil <seuil>) #<n>`.
|
||||
|
||||
Événements nommés Insights (macro `TRACE_CPUPROFILER_EVENT_SCOPE`, canal CPU ; en 5.5 il n'y a pas
|
||||
de macro publique `SCOPED_NAMED_EVENT`) : `LoadTxt`, `LPDataCache` (`TryGet`), `LPDataCache_LoadAll`.
|
||||
Ils sont vides en Shipping (`CPUPROFILERTRACE_ENABLED` est 0).
|
||||
|
||||
ChaosVD est limité à l'éditeur dans `LifePlay.uproject`. En jeu empaqueté, si
|
||||
`p.Chaos.StartVDRecording` n'existe pas, `LifePlay.CollisionDump` est la preuve collision. Pour un
|
||||
enregistrement CVD, ouvrir l'éditeur et attacher le débogueur visuel Chaos au processus, ou jouer
|
||||
en PIE.
|
||||
|
||||
Options déjà en place, inchangées : `-LPDump*`, `-LPTrace*`, `-LPNoDataCache`, `-LPDataCacheVerify`,
|
||||
console `LifePlay.LogSummary` et `ModdingExtra.LoadTxtStats`.
|
||||
|
||||
## 6. Gates
|
||||
|
||||
| Étape | Vert seulement si |
|
||||
|---|---|
|
||||
| Build | UAT code 0, `LifePlay.exe`, 6 lanceurs à côté, PDB du projet présents |
|
||||
| Hitch | Au moins une ligne `LPDebug hitch :`, ou `0 hitch(s)` après ≥ 2 min |
|
||||
| Signet | Ligne `LPDebug bookmark :` |
|
||||
| Mémoire | Fichier `MemSnapshot_*.txt` non vide |
|
||||
| Session A | `.utrace` ouvrable, signets alignés sur les hitches, 3 à 5 causes avec ms ou % |
|
||||
| Session B | Physique en Mo, MemReport ou snapshot, CVD **ou** CollisionDump, delta mémoire |
|
||||
| Session C | Au moins une tranche de 9 000 images, LLM, médiane ≤ 17 ms (±1) ou écart expliqué, images > 100 ms comparées à O0 (112). Jouer **sans** `-LPHitchLog` |
|
||||
|
||||
Preuves attendues : `Dbg-I4`, `Dbg-I5`, `Dbg-I6` sous `D:\Game\LifePlay_Backup_5.5\`.
|
||||
|
||||
## 7. Agent
|
||||
|
||||
```
|
||||
- [ ] Development pour les chiffres, DebugGame pour la plongée
|
||||
- [ ] BuildDebug.ps1 si l'archive n'existe pas ou si le C++ a changé
|
||||
- [ ] Une session (A, B ou C), pas un lanceur unique
|
||||
- [ ] CollectDebugArtifacts.ps1
|
||||
- [ ] Insights / SummarizeDebugCsv / ChaosVD selon la session
|
||||
- [ ] Rien de Tracy, Optick, ASAN, ImGui
|
||||
```
|
||||
|
||||
Outils et URLs : `.cursor/skills/lifeplay-debug-build/reference.md`.
|
||||
Priorité : Unreal Insights → CSV/LLM → ChaosVD → RenderDoc / PIX / suite Radeon / µProf / diagnostics Visual Studio.
|
||||
Écartés : Tracy, Optick, Superluminal, ASAN, Nsight par défaut (machine AMD).
|
||||
|
||||
## 8. Mesures du 2026-10-04 et décision
|
||||
|
||||
Build DebugGame : `D:\Game\LifePlay_Build_5.5_DebugGame` (UAT 243 s). Preuves :
|
||||
`D:\Game\LifePlay_Backup_5.5\Dbg-I4` et `Dbg-I5`. Les millisecondes DebugGame ne se comparent
|
||||
pas à O0.
|
||||
|
||||
### Session A (G6)
|
||||
|
||||
Trace CPU courte ouvrable (`SessionA_cpu.utrace`, 252 Mo, export
|
||||
`Dbg-I4\GameThreadTimers.txt`) : 5 388 images, 32 à-coups ≥ 50 ms, max **2 389 ms**.
|
||||
La trace avec canal `memory` (`SessionA.utrace`, 6,4 Go, 89 à-coups en 350 s, max 6,7 s)
|
||||
a saturé Insights (51 Gio / 63). Les signets `TRACE_BOOKMARK` sont émis dans la même
|
||||
fonction que chaque ligne `LPDebug hitch` ; le max 2,35 s des timers `FTimerManager` et
|
||||
`ExecuteUbergraph_City` est le même à-coup que le journal (2 389 ms).
|
||||
|
||||
Temps inclus GameThread, budget de travail `GameEngine Tick` = 14,42 s (l'attente de
|
||||
cadence, 69,6 s, est à part) :
|
||||
|
||||
| Cause | Inclus | Part du tick moteur | Max une fois |
|
||||
|---|---:|---:|---:|
|
||||
| `ExecuteUbergraph_GI` | 5,34 s | 37 % | 2,35 s |
|
||||
| `FTimerManager` | 3,39 s | 24 % | 2,35 s |
|
||||
| `ExecuteUbergraph_City` | 2,51 s | 17 % | 2,35 s |
|
||||
| `ExecuteUbergraph_Person` | 1,73 s | 12 % | 0,30 s |
|
||||
| `PopulateRaws` | 1,26 s | 9 % | 1,26 s |
|
||||
| Actions latentes Blueprint | 1,03 s | 7 % | 0,30 s |
|
||||
| `LoadTxt` | 5,5 ms | — | 0,2 ms |
|
||||
| `LPDataCache` | 2,6 ms | — | 0,2 ms |
|
||||
|
||||
`LoadTxt` n'est pas la source des à-coups. Le pic de 2,35 s est le graphe de `GI` / `City`
|
||||
déclenché par le gestionnaire de minuteries.
|
||||
|
||||
### Session B (physique)
|
||||
|
||||
LLM, fin de chargement de `CentralLondon` (textures 651 Mo, proche des 624 Mo d'O0) :
|
||||
|
||||
| Poste | Début | Fin |
|
||||
|---|---:|---:|
|
||||
| Physics | 6,3 Mo | **171,6 Mo** |
|
||||
| Physics/ChaosTrimesh | — | 87,6 Mo |
|
||||
| TrackedTotal | 843 Mo | 1 888 Mo |
|
||||
|
||||
Delta physique **+165 Mo**. `p.Chaos.StartVDRecording` a écrit
|
||||
`ChaosVD-20261004_130550.utrace` (172 Mo). Le `CollisionDump` du `ExecCmds` de démarrage
|
||||
compte 23 primitives et 1 trimesh, sans dire qui le porte. Le détail est au §10.
|
||||
MemReport et `MemSnapshot_20261004_130550.txt` sont dans `Dbg-I5\SessionB`.
|
||||
|
||||
Les 463 Mo d'O0 ne se retrouvent pas : les collisions décoratives ont été retirées en O9.
|
||||
La physique reste le second poste derrière les textures, plus le premier.
|
||||
|
||||
### Session C (Development, sans `-LPHitchLog`)
|
||||
|
||||
Build `D:\Game\LifePlay_Build_5.5_Development` (UAT 127 s). Une tranche complète :
|
||||
`Profile(20261004_131659).csv`, 9 000 images. Preuves : `Dbg-I6\SessionC`.
|
||||
|
||||
| Mesure | Cette tranche | O0 (36 004 images) |
|
||||
|---|---:|---:|
|
||||
| Médiane temps d'image | **16,66 ms** | 16,7 ms |
|
||||
| Médiane thread de jeu | 3,57 ms | 3,9 à 4,4 ms |
|
||||
| Images > 50 ms | 52 (0,58 %) | 159 (0,44 %) |
|
||||
| Images > 100 ms | 23 (0,26 %) | 112 (0,31 %) |
|
||||
|
||||
La médiane tient le plafond de 60 images/s (≤ 17 ms). Le taux d'images > 100 ms n'est pas
|
||||
plus haut qu'en O0. Le parcours n'est pas les 15 minutes du scénario O0 (démarrage puis
|
||||
`CentralLondon`), donc le décompte brut 23 contre 112 ne se compare pas tel quel. LLM en fin
|
||||
de cette tranche : textures 605 Mo, physique 89 Mo (la session B, plus longue, monte à 172 Mo).
|
||||
|
||||
### Décision
|
||||
|
||||
Prochain lot d'optimisation : **G6**, pas O10 en premier. O10 reste un lot mémoire
|
||||
(trimesh ~88 Mo en session B), après G6. Le sous-système debug, journal d'à-coups éteint,
|
||||
ne déplace pas la médiane Development. La trace Development du §9 nomme la feuille :
|
||||
ce n'est pas le graphe `GI` qu'il faut réécrire.
|
||||
|
||||
## 9. Trace Development courte (G6, 2026-10-04)
|
||||
|
||||
Build déjà en place : `D:\Game\LifePlay_Build_5.5_Development`. Trace
|
||||
`Windows\LifePlay\Saved\Profiling\G6Dev.utrace` (464 Mo), canaux
|
||||
`cpu,frame,bookmark,loadtime`, sans `memory`, avec `-LPHitchLog`. Durée 184 s, arrêt
|
||||
après le premier à-coup > 500 ms. Copies : `D:\Game\LifePlay_Backup_5.5\Dbg-G6`.
|
||||
|
||||
Un seul à-coup > 500 ms : **3 621 ms** à l'image du clic Continuer (`DelayedStart`,
|
||||
3,583 s). Le minuteur de 2 s qui rappelle `DelayedStart` dure ~13 µs. Aucun
|
||||
`FTimerManager_Tick` > 200 ms : le 654 ms de l'image 3137 (CSV I6) n'est pas reproduit
|
||||
sur ce démarrage automatique.
|
||||
|
||||
Feuille CPU, temps exclusif dans cette image (export `ExportTimerCallees`, thread de jeu) :
|
||||
`FindCenterOfPolygon` **0,350 s** exclusif / 0,742 s inclus, 49 425 appels (centre de la
|
||||
boîte englobante, pas le centroïde). `Array_Get` est plus haut en exclusif (0,536 s) mais
|
||||
c'est un opcode de la machine virtuelle, pas une fonction. `PopulateRaws` et
|
||||
`ExecuteUbergraph_City` restent des contenants (exclusif négligeable).
|
||||
|
||||
Lecture des ~164 Mo (CSV I6, image 395, colonnes exclusives vides) : le canal `loadtime`
|
||||
nomme `/Game/Heavy/StreetMap/Static/CentralLondon` et
|
||||
`/Game/Heavy/StreetMap/CentralLondon`. Disque chaud, l'attente synchrone de cette trace
|
||||
est 0,325 s (`GetAsset` × 2 → `MainThreadWaitingOnAsyncLoadingThread`).
|
||||
|
||||
Deux lots, baseline = la tranche I6 (médiane 16,66 ms, une image à 6,2 s, une image
|
||||
TimerManager à 654 ms) :
|
||||
|
||||
- CHG-115 : `FindCenterOfPolygon` en C++, quatre appels de `GI` rebranchés. Pas de
|
||||
réécriture du graphe, pas de décalage des minuteries.
|
||||
- CHG-116 : préchargement async de ces deux paquets à l'init de l'instance de jeu.
|
||||
Séparé du correctif de la feuille.
|
||||
|
||||
Cuisson Development du 2026-10-04 14:26 (`BuildDebug_Development_20261004-142617.log`,
|
||||
UAT 148 s) contient les deux lots. Partie 14:31–14:46, quittée normalement, sans
|
||||
`-LPHitchLog` et sans CSV (journal archivé `Dbg-G6p\SessionC`). `Preload ville :
|
||||
demande envoyee` dès l'init. Aucune erreur `FindCenterOfPolygon`. L'image du clic
|
||||
Continuer (`StreetMap BeginPlay`, puis `PopulateRaws`) s'étale sur **1 017 ms** de
|
||||
journal, contre 3 621 ms avant ces lots. Une autre image, à 14:33:02 (entrée
|
||||
« Central London » / pièce `street`), s'étale sur 1 171 ms. Ce ne sont pas des temps
|
||||
CSV : la médiane 16,66 ms et le rythme d'O0 restent à remesurer avec le lanceur
|
||||
de la session C.
|
||||
|
||||
## 10. Rapport collisions (2026-10-04)
|
||||
|
||||
Les capsules des PNJ ne sont pas les 88 Mo. Un seul trimesh les explique, et le dump
|
||||
de démarrage ne nomme pas son acteur. `-LPCollisionLog` (CHG-117) est dans la cuisson
|
||||
de 15:16. La partie de 15:19 ne l'a pas activé : `LifePlay.exe` a été lancé sans ce switch.
|
||||
|
||||
| Preuve | Ce qu'elle montre |
|
||||
|---|---|
|
||||
| `Dbg-I5\SessionB\Profiling\LPDebug\CollisionDump_20261004_130550.txt` | Monde `CentralLondon`, 23 primitives dont **12 `Person_C`**, 6 `Room_C`, 2 `StreetMap_Static_C`, 1 `StaticMeshActor`. **1 trimesh** unique, 3 formes simples, 0 corps simulé. Pas de nombre de triangles ni d'acteur porteur. |
|
||||
| `Dbg-I5\SessionB\Profiling\LLM\LLM_Pid12232_2026.10.04-13.05.50_Windows.csv` | 402 échantillons. Maximum : physique **176,7 Mo**, trimesh **93,5 Mo**, `ChaosUpdate` **83,0 Mo**, convexes **0,11 Mo**. Dès l'échantillon 17 le trimesh est déjà à 79,3 Mo. |
|
||||
| `Dbg-I5\SessionB\Profiling\ChaosVD-20261004_130550.utrace` (180 Mo) | Enregistrement présent, non ouvert : il ne nomme pas le maillage. |
|
||||
| `Dbg-I6\SessionC\Profiling\CSV\Profile(20261004_131659).csv` | Development, 9 000 images, médiane **16,66 ms**. Image 395 : **6 218 ms** et 164 Mo lus. Image 3137 : **664 ms** dans `TimerManager`. |
|
||||
| `Dbg-G6p\SessionC\Profiling\G6Dev.utrace` (464 Mo ; original dans le Saved du build Development) | Canaux `cpu,frame,bookmark,loadtime`. Image Continuer **3 621 ms**. Feuille `FindCenterOfPolygon` 0,350 s exclusif. Paquets `/Game/Heavy/StreetMap/Static/CentralLondon` et `/Game/Heavy/StreetMap/CentralLondon`. |
|
||||
| `Dbg-G6p\SessionC\Logs\LifePlay.log` | Partie 14:31–14:46 avec CHG-115/116. Préchargement envoyé. Image Continuer **1 017 ms** de journal. Entrée dans la ville **1 171 ms**. Pas de relevé de collision (lanceur sans `-LPCollisionLog`). |
|
||||
|
||||
Lecture. Les 12 capsules de PNJ sont des formes simples : les convexes du LLM culminent à 0,11 Mo. Le trimesh (93 Mo) et `ChaosUpdate` (83 Mo) apparaissent ensemble et font l'essentiel des 177 Mo de physique. Le dump de la même session ne voit qu'un trimesh, donc ce poste est un maillage complexe unique, parmi `StreetMap_Static_C`, `Room_C` ou le `StaticMeshActor`, pas une capsule de personnage. `StreetMap` sait construire cette collision (`UseComplexAsSimple`, profil `BlockAll`) ; le défaut du composant est de ne pas la générer, et le dump ne dit pas si l'instance de la ville l'a fait.
|
||||
|
||||
Le CVD et le dump de démarrage ne suffisent pas à désigner l'acteur. Le suivi `-LPCollisionLog` écrit `CollisionTrack_*.txt` (une ligne toutes les 2 s) et un `CollisionDump_*.txt` dès que des triangles sont comptés, avec la classe, l'acteur et le composant. Les lanceurs `LifePlay_debug_sessionB_mem.bat` et `LifePlay_debug_collision.bat` passent ce switch et ne lancent plus `LifePlay.CollisionDump` dans le `ExecCmds` de démarrage.
|
||||
|
||||
Partie de la cuisson 15:16, 15:19–15:27, sortie normale (`Dbg-G6r\LifePlay.log`). Lignes au démarrage : `LPDebug collision : inactif`, `Preload ville : demande envoyee`, aucune erreur `FindCenterOfPolygon`. Une image, à 15:20:00, s'étale sur **3 152 ms** de journal : `StreetMap BeginPlay`, puis les fichiers de monde, puis « Central London », puis le domicile (`home test310 UpdateWall`) et un clic. Environ **2 s** de cette image n'ont presque pas de lignes. Ce n'est pas un temps CSV. Le porteur du trimesh reste non nommé tant que la partie n'est pas lancée par `LifePlay_debug_collision.bat`.
|
||||
|
||||
Partie 15:37–15:50 avec `-LPCollisionLog` (réglage Collisions du lanceur). Sortie normale. 382 échantillons, 4 dumps, maximum **1 206 768** triangles (`Dbg-G6s`). À 7 s, le porteur est nommé :
|
||||
|
||||
| Triangles | Acteur | Composant | Profil |
|
||||
|---:|---|---|---|
|
||||
| **1 084 249** | `StreetMap_Static` | `CentralLondon` | `BlockAllDynamic` |
|
||||
| 122 469 | `Room_C_0` (au pic, 61 primitives) | meubles `fnt_new*`, lampes `ls_lamp*` | `Custom` |
|
||||
| 0 | 84 `Person_C` | une capsule `CollisionCylinder` partagée | `Custom` |
|
||||
|
||||
Le maillage de rue porte **90 %** des triangles de collision. Les PNJ n'en ont aucun. Preuve : `CollisionDump_20261004_153727.txt`. En fin de partie il reste 1 143 395 triangles. L'enregistrement ChaosVD (299 Mo) est dans le Saved du build.
|
||||
285
Docs/Migration/19_Plan_Outillage_Debug.md
Normal file
285
Docs/Migration/19_Plan_Outillage_Debug.md
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
# 19 — Plan d'outillage debug (pilotage)
|
||||
|
||||
> Plan de lot révisé (game manager + senior). Procédure opérationnelle et mesures :
|
||||
> [`18_Build_Debug.md`](18_Build_Debug.md). Skill agent :
|
||||
> [`.cursor/skills/lifeplay-debug-build/`](../../.cursor/skills/lifeplay-debug-build/).
|
||||
> Protocole lots : [`10_Protocole_Operations.md`](10_Protocole_Operations.md).
|
||||
|
||||
**État :** gates I0–I6 mesurés (voir §4 et relevés dans `18`). Prochain lot d'optimisation
|
||||
proposé : **G6** (pas O10 en premier) — décision dans `18` § mesures.
|
||||
|
||||
---
|
||||
|
||||
## 1. Brief production (game manager)
|
||||
|
||||
### Mission
|
||||
|
||||
Livrer une **capacité de mesure reproductible** (pipeline + preuves), pas une optimisation joueur. Après ce lot, l'équipe peut décider *quoi* corriger (G6 à-coups, O10 physique) avec des chiffres, pas au feeling.
|
||||
|
||||
### Succès (Definition of Done globale)
|
||||
|
||||
Le lot est **terminé** seulement si :
|
||||
|
||||
1. Un tiers (ou un agent futur) peut packager DebugGame/Development et lancer sessions A/B/C **sans scripts hors dépôt**.
|
||||
2. Trois jeux de preuves archivés (`Dbg-I4`, `Dbg-I5`, `Dbg-I6`) sous `D:\Game\LifePlay_Backup_5.5\`.
|
||||
3. Une note chiffrée classe les priorités suivantes (G6 vs O10 vs autre) — **décision**, pas encore le fix.
|
||||
4. Shipping **inchangé** en comportement joueur (`IncludeDebugFiles=False`, pas de coût runtime Shipping).
|
||||
5. Skill + `18_Build_Debug.md` alignés sur le livré.
|
||||
|
||||
### Hors scope (explicitement)
|
||||
|
||||
| Inclus | Exclu jusqu'à preuves I4–I6 |
|
||||
|---|---|
|
||||
| Scripts packaging / lanceurs / collecte | Optimisation GI / timers / async load |
|
||||
| Sous-système debug non-Shipping | Refonte collisions ville / LOD / morphs |
|
||||
| Doc + skill | Montée UE 5.8 (préparer seulement la portabilité) |
|
||||
| Mesure G6 / Physics | Intégration Tracy / Optick / ASAN / ImGui |
|
||||
| Outils gratuits documentés | Builds Shipping « debug » pour joueurs |
|
||||
|
||||
### Priorité joueur (pourquoi ce lot)
|
||||
|
||||
| Signal existant (O0/O9) | Impact ressenti | Ce lot livre |
|
||||
|---|---|---|
|
||||
| À-coups GameThread (G6) | Stutters irréguliers | Preuve nommée → backlog fix priorisé |
|
||||
| Physics ~463 Mo (O0) ; ~172 Mo session B post-O9 | Risque machines modestes | Chiffre Physics + CVD → backlog O10 |
|
||||
| Régime 16,7 ms | OK aujourd'hui | Garde-fou non-régression (I6) |
|
||||
|
||||
**Règle produit :** ne pas ouvrir un lot d'optimisation tant que I4 (hitches) **et** I5 (mémoire) n'ont pas leurs gates verts.
|
||||
|
||||
### Effort et dépendances (ordre de grandeur)
|
||||
|
||||
| Phase | Effort estimé | Qui | Dépendance |
|
||||
|---|---|---|---|
|
||||
| I0 skill | 15 min | Agent | — |
|
||||
| I1 scripts + 1er cook DebugGame | 0,5–1 j (+ cook long) | Agent ; cook machine | UE 5.5, Content/Heavy dispo |
|
||||
| I2 C++ + compile / smoke | 0,5 j | Agent | I1 ou Editor Development pour smoke |
|
||||
| I3 doc + sync skill | 0,25 j | Agent | I1–I2 stabilisés |
|
||||
| I4 / I5 / I6 playtests | 3× ~20–30 min jeu + analyse | **Utilisateur** joue ; agent archive/analyse | Builds prêtes |
|
||||
|
||||
**Chemin critique :** temps de cook DebugGame + disponibilité playtest humain.
|
||||
|
||||
### Risques production
|
||||
|
||||
| Risque | Impact | Mitigation |
|
||||
|---|---|---|
|
||||
| Cook DebugGame très long / échec | Bloque I1 | Smoke I2 en Editor d'abord ; `-SkipBuild` / iterate ; un seul cook DebugGame pour I4–I5 |
|
||||
| Confusion DebugGame vs Dev sur les ms | Fausses régressions | Dual-build documenté ; I6 = Development only vs O0 |
|
||||
| Instrumentation fuit dans Shipping | Perf / logs joueur | `#if !UE_BUILD_SHIPPING` strict ; pas de changement `DefaultGame.ini` Shipping |
|
||||
| Scope creep « on corrige GI tout de suite » | Lot interminable | Hors scope ; nouveau lot après I6 |
|
||||
| Trace Insights trop lourde | Machine saturée (déjà vu O9) | Trace courte ciblée ; channels limités ; bookmarks |
|
||||
| Playtest non standard | Mesures incomparables | Scénario O0 figé 15 min ; même type de save |
|
||||
|
||||
### Gouvernance
|
||||
|
||||
- Protocole lots : [`10_Protocole_Operations.md`](10_Protocole_Operations.md) (tag, snapshot, journal).
|
||||
- Skill obligatoire : [`.cursor/skills/lifeplay-debug-build/`](../../.cursor/skills/lifeplay-debug-build/).
|
||||
- **Go / No-Go** à chaque gate In : critères verts ou stop + re-mesure.
|
||||
- Commit : uniquement si demandé ; un sous-lot = un objectif mesuré.
|
||||
|
||||
---
|
||||
|
||||
## 2. Brief technique (senior gameplay / engine)
|
||||
|
||||
### Principes
|
||||
|
||||
1. **Zero cost Shipping** — tout debug derrière `!UE_BUILD_SHIPPING` ; flags CLI opt-in.
|
||||
2. **Réutiliser** `ULPSessionLogSubsystem` patterns (`FParse`, `FAutoConsoleCommand`, `FName` categories).
|
||||
3. **Pas de modification Blueprint GI** dans ce lot — C++ subsystem + ModdingExtra hotspots only.
|
||||
4. **DebugGame rare** — un cook pour plongée ; itération code en Editor/Development.
|
||||
5. **Portable 5.8** — Insights/ChaosVD/CSV + attach process ; pas de plugin profiler tiers.
|
||||
|
||||
### Architecture cible
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph mgmt [DoD_produit]
|
||||
Proof[Preuves_A_B_C]
|
||||
Decision[Priorite_G6_vs_O10]
|
||||
end
|
||||
subgraph pipe [Pipeline]
|
||||
BuildPs[BuildDebug.ps1]
|
||||
Bats[Tools/debug_bats]
|
||||
Collect[CollectDebugArtifacts.ps1]
|
||||
end
|
||||
subgraph runtime [Runtime_non_Shipping]
|
||||
LPDebug[ULPDebugSubsystem]
|
||||
Named[SCOPED_NAMED_EVENT_LoadTxt_Cache]
|
||||
SessLog[ULPSessionLogSubsystem_existant]
|
||||
end
|
||||
BuildPs --> Bats
|
||||
Bats --> LPDebug
|
||||
Bats --> SessLog
|
||||
Named --> Insights[UnrealInsights]
|
||||
LPDebug --> Insights
|
||||
Bats --> Collect
|
||||
Collect --> Proof
|
||||
Proof --> Decision
|
||||
```
|
||||
|
||||
### Dual-build
|
||||
|
||||
| Config | Rôle | Comparer ms absolus |
|
||||
|---|---|---|
|
||||
| **Development** | Baseline chiffrée / I6 vs O0 | Oui |
|
||||
| **DebugGame** | Plongée I2/I4/I5 (checks, PDB) | Non |
|
||||
| **Shipping** | Joueurs | Hors lot |
|
||||
|
||||
### Livrables techniques
|
||||
|
||||
| Livrable | Contenu clé |
|
||||
|---|---|
|
||||
| [`Tools/BuildDebug.ps1`](../../Tools/BuildDebug.ps1) | `-Config DebugGame\|Development`, pas `-nodebuginfo`, stage des bats |
|
||||
| [`Tools/CollectDebugArtifacts.ps1`](../../Tools/CollectDebugArtifacts.ps1) | `-Lot -BuildDir -Session` → backup |
|
||||
| `Tools/debug/*.bat` | Sessions A/B/C + bugs/gpu/collision |
|
||||
| `ULPDebugSubsystem` | Hitch + bookmark + MemSnapshot + CollisionDump |
|
||||
| Named events | `LoadTxt`, `LPDataCache` |
|
||||
| [`18_Build_Debug.md`](18_Build_Debug.md) | Procédure + mesures + Agent/skill |
|
||||
|
||||
### Sessions de capture
|
||||
|
||||
Scénario unique (O0 §5, ~15 min) : save avancée → carte → intérieur → extérieur → multi-PNJ → quit.
|
||||
|
||||
| Session | Build | Gate métier |
|
||||
|---|---|---|
|
||||
| A | DebugGame | Top contributeurs hitch **chiffrés** |
|
||||
| B | DebugGame | Physics Mo + CVD ou dump collision |
|
||||
| C | Development | Non-régression vs O0 (médiane ~16,7 ms) |
|
||||
|
||||
Auxiliaires hors chemin critique : bugs / gpu (PIX **ou** RGP) / collision.
|
||||
|
||||
### Stack outils (gratuite, pas de trial)
|
||||
|
||||
Insights + Memory Insights, CSV/LLM, ChaosVD, RenderDoc, PIX, Radeon RGP/RMV, µProf, VS Diagnostics.
|
||||
Écartés : Tracy, Optick, ASAN, Superluminal, Nsight par défaut (machine AMD).
|
||||
|
||||
Détail : [`.cursor/skills/lifeplay-debug-build/reference.md`](../../.cursor/skills/lifeplay-debug-build/reference.md).
|
||||
|
||||
### Pièges techniques connus
|
||||
|
||||
- Jamais `-csvCaptureFrames` (crash RHI).
|
||||
- Mesurer packaged, pas Editor `-game`, pour I6.
|
||||
- Shipping `NO_LOGGING` — ne pas s'y fier pour l'analyse.
|
||||
- Traces Insights lourdes : rester court + bookmarks.
|
||||
- I6 **sans** `-LPHitchLog` (overhead off by default).
|
||||
|
||||
---
|
||||
|
||||
## 3. Skills (obligatoire agent)
|
||||
|
||||
| Skill | Usage |
|
||||
|---|---|
|
||||
| [`SKILL.md`](../../.cursor/skills/lifeplay-debug-build/SKILL.md) | Lire **avant** tout code (I0) |
|
||||
| [`reference.md`](../../.cursor/skills/lifeplay-debug-build/reference.md) | Flags, chemins, mapping symptôme→outil |
|
||||
|
||||
Ne pas diverger (mega-lanceur, Tracy, rebuild DebugGame systématique) sans maj skill + ce plan.
|
||||
|
||||
---
|
||||
|
||||
## 4. Planning itératif mesurable
|
||||
|
||||
### Règle de sortie
|
||||
|
||||
> On ne quitte **In** que si **tous** les critères de mesure sont verts.
|
||||
> Code sans artefact / impression en jeu = **rouge**.
|
||||
> Rouge → corriger → **re-mesurer**. Pas de skip vers In+1.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
I0[I0] --> I1[I1]
|
||||
I1 --> G1{Gate}
|
||||
G1 -->|non| I1
|
||||
G1 -->|oui| I2[I2]
|
||||
I2 --> G2{Gate}
|
||||
G2 -->|non| I2
|
||||
G2 -->|oui| I3[I3]
|
||||
I3 --> G3{Gate}
|
||||
G3 -->|non| I3
|
||||
G3 -->|oui| I4[I4]
|
||||
I4 --> G4{Gate}
|
||||
G4 -->|non| I4
|
||||
G4 -->|oui| I5[I5]
|
||||
I5 --> G5{Gate}
|
||||
G5 -->|non| I5
|
||||
G5 -->|oui| I6[I6]
|
||||
I6 --> G6{Gate}
|
||||
G6 -->|non| I6
|
||||
G6 -->|oui| DoD[DoD_lot]
|
||||
```
|
||||
|
||||
### Statut des gates
|
||||
|
||||
| Itération | Objectif | Gate (résumé) | Statut |
|
||||
|---|---|---|---|
|
||||
| I0 | Skill | Procédure partagée | Fait |
|
||||
| I1 | Pipeline scripts | Build + Collect smoke | Fait |
|
||||
| I2 | `ULPDebugSubsystem` | Hitch / bookmark / MemSnapshot | Fait |
|
||||
| I3 | Doc 18 + skill sync | Doc = réalité | Fait |
|
||||
| I4 | Session A | utrace + hitches chiffrés G6 | Fait — voir `18` |
|
||||
| I5 | Session B | Physics Mo + CVD | Fait — voir `18` |
|
||||
| I6 | Session C vs O0 | Médiane ≤ 17 ms, hitches | Fait — voir `18` |
|
||||
|
||||
### Détail des itérations
|
||||
|
||||
#### I0 — Alignement
|
||||
|
||||
- **Owner :** Agent
|
||||
- **Mesure :** skill lu ; scope hors Tracy/mega-lanceur confirmé
|
||||
|
||||
#### I1 — Pipeline (P0 technique)
|
||||
|
||||
- **Livrables :** `BuildDebug.ps1`, `CollectDebugArtifacts.ps1`, bats
|
||||
- **Gate :** UAT DebugGame OK + exe + bats staged + Collect smoke → backup non vide
|
||||
|
||||
#### I2 — Runtime debug (P0 technique)
|
||||
|
||||
- **Livrables :** `ULPDebugSubsystem` + named events ; Shipping-safe
|
||||
- **Gate :** compile clean ; hitch log ; Bookmark ; MemSnapshot non vide
|
||||
|
||||
#### I3 — Doc / skill = réalité
|
||||
|
||||
- **Gate :** noms bats/flags identiques doc↔skill↔Tools ; section Agent ; piège CSV documenté
|
||||
|
||||
#### I4 — Mesure G6 hitches (P0 produit)
|
||||
|
||||
- **Owner :** Utilisateur (jeu) + Agent (analyse)
|
||||
- **Gate :** `.utrace` ; bookmarks/hitch alignés ; top contributeurs avec ms/% ; Collect OK
|
||||
- **Livrable décision :** causes candidates classées
|
||||
|
||||
#### I5 — Mesure Physics / fuites (P0 produit)
|
||||
|
||||
- **Gate :** Physics Mo ; MemReport ; CVD ou CollisionDump ; delta mémoire ; Collect OK
|
||||
|
||||
#### I6 — Non-régression Development
|
||||
|
||||
- **Gate :** CSV ≥1×9000 frames ; LLM ; tableau vs O0 ; médiane ≤17 ms (±1) ou écart causé ; Collect OK
|
||||
- **Sans** `-LPHitchLog`
|
||||
|
||||
### Après DoD
|
||||
|
||||
Lots suivants (chacun avec baseline avant/après) :
|
||||
|
||||
1. **Fix G6** (étalement `ExecuteUbergraph_GI` / `City`, `PopulateRaws`) — priorité décidée en I4/I6
|
||||
2. O10 collisions / trimesh — après G6
|
||||
3. Montée 5.8 — réutiliser cette pipeline
|
||||
|
||||
---
|
||||
|
||||
## 5. Critères d'acceptation (checklist manager)
|
||||
|
||||
- [x] Aucun changement comportement Shipping joueur
|
||||
- [x] Scripts versionnés ; plus de dépendance à `LifePlay_profil_O0.bat` hors repo
|
||||
- [x] I4 + I5 + I6 verts avec backups
|
||||
- [x] Décision écrite G6 vs O10 (priorité prochain lot) — dans [`18_Build_Debug.md`](18_Build_Debug.md)
|
||||
- [x] Skill + doc 18 synchronisés
|
||||
- [ ] Temps cook / playtest consigné au journal (`Journal_Modifications.md`) si publication du lot
|
||||
|
||||
---
|
||||
|
||||
## 6. Ordre d'exécution (référence)
|
||||
|
||||
1. I0 skill
|
||||
2. I1 scripts ; cook DebugGame
|
||||
3. I2 C++ ; recook DebugGame si besoin
|
||||
4. I3 doc + sync skill
|
||||
5. I4 → gate → I5 → gate → I6 → gate
|
||||
6. Revue DoD + proposition du prochain lot d'optimisation
|
||||
|
|
@ -28,6 +28,8 @@ La migration a été reprise le 2026-09-29, après l'échec d'un premier portage
|
|||
| [15_Modules_Optionnels.md](15_Modules_Optionnels.md) | lot | build Shipping réduite aux modules essentiels, modules optionnels dans une archive séparée (`Tools/SplitModules.ps1`) |
|
||||
| [16_H3_Mods_Paquets.md](16_H3_Mods_Paquets.md) | étude, prototype | H3 : assets apportés par les mods (plugins `Mods/`, cuisson DLC contre la version publiée) ; constats, architecture, risques, lots H3a à H3c ; §5 résultats du prototype H3a, §6 outillage H3b (`ModSources/`, `Tools/BuildMod.ps1`), validés ; §7 guide des moddeurs (`Docs/Modding/FullGuides/Creating_Asset_Mods.md`) et publication ; §8 métadonnées, avertissement de version, paquet `lpr_hair_pocolov` (H3d) |
|
||||
| [17_Mods_Ecran_Titre.md](17_Mods_Ecran_Titre.md) | lot | UI15 : gestionnaire de mods depuis l'écran titre (entrée dédiée, retour au menu titre, choix enregistré) |
|
||||
| [18_Build_Debug.md](18_Build_Debug.md) | mesure | builds DebugGame / Development, sessions Insights, mémoire, CSV ; Shipping inchangé |
|
||||
| [19_Plan_Outillage_Debug.md](19_Plan_Outillage_Debug.md) | pilotage | plan du lot outillage debug (DoD, hors scope, gates I0–I6, décision G6 vs O10) |
|
||||
| [Journal_Modifications.md](Journal_Modifications.md) | traçabilité | `CHG-001` à `CHG-057`, chacune avec son retour arrière |
|
||||
| [Registre_Erreurs.md](Registre_Erreurs.md) | traçabilité | `ERR-001` à `ERR-037`, statut et lot |
|
||||
| `correctifs/` | relecture | écarts de code 4.27 → 5.5 en patchs (`P01` à `P06`) |
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@
|
|||
#include "Serialization/JsonReader.h"
|
||||
#include "Serialization/JsonSerializer.h"
|
||||
#include "Serialization/JsonWriter.h"
|
||||
#if !UE_BUILD_SHIPPING
|
||||
#include "ProfilingDebugging/CpuProfilerTrace.h"
|
||||
#endif
|
||||
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogLPDataCache, Log, All);
|
||||
|
||||
|
|
@ -92,6 +95,9 @@ void FLPDataCache::StartAsync()
|
|||
|
||||
void FLPDataCache::LoadAll()
|
||||
{
|
||||
#if !UE_BUILD_SHIPPING
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE(LPDataCache_LoadAll);
|
||||
#endif
|
||||
const double Start = FPlatformTime::Seconds();
|
||||
const FString ContentDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir());
|
||||
const FString ModulesDir = ContentDir / ModulesFolder;
|
||||
|
|
@ -233,6 +239,9 @@ void FLPDataCache::LoadAll()
|
|||
|
||||
bool FLPDataCache::TryGet(const FString& ContentRelativePath, FString& OutText)
|
||||
{
|
||||
#if !UE_BUILD_SHIPPING
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE(LPDataCache);
|
||||
#endif
|
||||
if (bDisabled || !bReady)
|
||||
{
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@
|
|||
#include "Modules/ModuleManager.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "Misc/Paths.h"
|
||||
#if !UE_BUILD_SHIPPING
|
||||
#include "ProfilingDebugging/CpuProfilerTrace.h"
|
||||
#endif
|
||||
|
||||
// Journal des entrees/sorties de donnees (lot L3 du portage 5.5, remplace l'instrumentation
|
||||
// temporaire LogLPModdingTrace du 2026-09-28).
|
||||
|
|
@ -177,6 +180,9 @@ namespace LPLoadTxtStats
|
|||
|
||||
bool UModdingExtraBPLibrary::LoadTxt(FString FileNameA, FString& SaveTextA)
|
||||
{
|
||||
#if !UE_BUILD_SHIPPING
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE(LoadTxt);
|
||||
#endif
|
||||
const double StartTime = FPlatformTime::Seconds();
|
||||
// Couche de donnees (etapes 2 et 3) : fichiers de Modules servis depuis la memoire quand le cache est pret.
|
||||
FLPDataCache& Cache = FLPDataCache::Get();
|
||||
|
|
|
|||
501
Source/LifePlay/LPDebugSubsystem.cpp
Normal file
501
Source/LifePlay/LPDebugSubsystem.cpp
Normal file
|
|
@ -0,0 +1,501 @@
|
|||
// 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
|
||||
52
Source/LifePlay/LPDebugSubsystem.h
Normal file
52
Source/LifePlay/LPDebugSubsystem.h
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// Portage UE 5.5 - lot debug (Docs/Migration/18_Build_Debug.md).
|
||||
//
|
||||
// Mesure opt-in, absente du comportement Shipping : journal des a-coups, signets Insights,
|
||||
// instantane memoire, releve de collisions. Aucun Blueprint modifie.
|
||||
// Inactif tant que -LPHitchLog ou -LPCollisionLog n'est pas passe.
|
||||
// -LPCollisionLog echantillonne la collision du monde joue (pas au ExecCmds de demarrage)
|
||||
// et ecrit un dump des qu'un trimesh est present, avec le nombre de triangles par acteur.
|
||||
// Commandes console (non-Shipping) : LifePlay.MemSnapshot, LifePlay.CollisionDump,
|
||||
// LifePlay.Bookmark, LifePlay.HitchSummary.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Subsystems/GameInstanceSubsystem.h"
|
||||
#include "LPDebugSubsystem.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class LIFEPLAY_API ULPDebugSubsystem : public UGameInstanceSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
virtual void Deinitialize() override;
|
||||
|
||||
/** Bilan des a-coups. When : "2 min", "fin" ou "console". */
|
||||
void LogHitchSummary(const TCHAR* When) const;
|
||||
|
||||
private:
|
||||
void OnEndFrame();
|
||||
void SampleCollision();
|
||||
void LogCollisionSummary(const TCHAR* When) const;
|
||||
|
||||
bool bHitchLog = false;
|
||||
float HitchMs = 50.f;
|
||||
int32 HitchCount = 0;
|
||||
int32 HitchLines = 0;
|
||||
double MaxHitchMs = 0.0;
|
||||
double StartSeconds = 0.0;
|
||||
double LastFrameSeconds = 0.0;
|
||||
bool bLoggedTwoMinuteSummary = false;
|
||||
|
||||
bool bCollisionLog = false;
|
||||
double NextCollisionSeconds = 0.0;
|
||||
int32 CollisionSamples = 0;
|
||||
int32 CollisionDumps = 0;
|
||||
int32 PeakPrim = 0;
|
||||
int64 PeakTriangles = 0;
|
||||
int64 LastDumpedTriangles = -1;
|
||||
int32 LastDumpedTriMeshes = -1;
|
||||
FString CollisionTrackPath;
|
||||
};
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
bool ULPModSettingsLibrary::SaveModSelection(UObject* GameInstance)
|
||||
{
|
||||
static const TCHAR* Slot = TEXT("GameSettings");
|
||||
static const FName ModEnabledsName(TEXT("ModEnableds"));
|
||||
static const FName SettingsModEnabledsName(TEXT("ModEnableds"));
|
||||
if (!GameInstance)
|
||||
{
|
||||
return false;
|
||||
|
|
@ -21,8 +21,8 @@ bool ULPModSettingsLibrary::SaveModSelection(UObject* GameInstance)
|
|||
return false;
|
||||
}
|
||||
USaveGame* Settings = UGameplayStatics::LoadGameFromSlot(Slot, 0);
|
||||
const FArrayProperty* From = CastField<FArrayProperty>(GameInstance->GetClass()->FindPropertyByName(ModEnabledsName));
|
||||
const FArrayProperty* To = Settings ? CastField<FArrayProperty>(Settings->GetClass()->FindPropertyByName(ModEnabledsName)) : nullptr;
|
||||
const FArrayProperty* From = CastField<FArrayProperty>(GameInstance->GetClass()->FindPropertyByName(SettingsModEnabledsName));
|
||||
const FArrayProperty* To = Settings ? CastField<FArrayProperty>(Settings->GetClass()->FindPropertyByName(SettingsModEnabledsName)) : nullptr;
|
||||
if (!From || !To || !From->SameType(To))
|
||||
{
|
||||
UE_LOG(LogLifePlay, Warning, TEXT("Modules : %s.sav illisible ou sans ModEnableds compatible, choix non enregistre"), Slot);
|
||||
|
|
|
|||
79
Tools/BuildDebug.ps1
Normal file
79
Tools/BuildDebug.ps1
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Empaquette LifePlay en DebugGame ou Development et depose les lanceurs de session.
|
||||
|
||||
.DESCRIPTION
|
||||
N'utilise jamais -nodebuginfo : les PDB du projet sont recopies dans l'archive
|
||||
(IncludeDebugFiles reste False dans DefaultGame.ini, le Shipping n'est pas touche).
|
||||
Les .bat de Tools\debug sont copies a cote de Windows\LifePlay.exe.
|
||||
|
||||
.EXAMPLE
|
||||
.\Tools\BuildDebug.ps1
|
||||
.\Tools\BuildDebug.ps1 -Config Development
|
||||
.\Tools\BuildDebug.ps1 -Config DebugGame -SkipBuild
|
||||
#>
|
||||
param(
|
||||
[ValidateSet('DebugGame', 'Development')]
|
||||
[string]$Config = 'DebugGame',
|
||||
[string]$ArchiveDir = '',
|
||||
[string]$UE = 'D:\Game\Launcher\Engine\UE_5.5\Engine',
|
||||
[string]$ProjectRoot = (Split-Path $PSScriptRoot -Parent),
|
||||
[switch]$SkipBuild
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if (-not $ArchiveDir) {
|
||||
$ArchiveDir = "D:\Game\LifePlay_Build_5.5_$Config"
|
||||
}
|
||||
|
||||
$proj = Join-Path $ProjectRoot 'LifePlay.uproject'
|
||||
$uat = Join-Path $UE 'Build\BatchFiles\RunUAT.bat'
|
||||
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
|
||||
$logDir = Join-Path $ProjectRoot 'Saved\Logs\portage'
|
||||
New-Item -ItemType Directory -Force $logDir | Out-Null
|
||||
$log = Join-Path $logDir "BuildDebug_${Config}_$stamp.log"
|
||||
|
||||
$buildFlag = ''
|
||||
if (-not $SkipBuild) { $buildFlag = '-build ' }
|
||||
|
||||
# Pas de -nodebuginfo. -pak -iostore -compressed : reglages courants du projet (lot O2).
|
||||
# Fichier .cmd : PowerShell couperait une ligne UAT mal quotee (meme parade que BuildMod.ps1).
|
||||
$batch = [IO.Path]::ChangeExtension($log, '.cmd')
|
||||
$cmd = "@call `"$uat`" BuildCookRun -project=`"$proj`" -platform=Win64 -clientconfig=$Config $buildFlag-cook -iterate -stage -pak -iostore -compressed -archive -archivedirectory=`"$ArchiveDir`" -unattended -utf8output -nop4 > `"$log`" 2>&1"
|
||||
[IO.File]::WriteAllText($batch, $cmd + "`r`n@exit /b %ERRORLEVEL%`r`n", (New-Object Text.UTF8Encoding $false))
|
||||
Write-Host "> $cmd"
|
||||
$t0 = Get-Date
|
||||
& cmd /c $batch
|
||||
$code = $LASTEXITCODE
|
||||
$dur = [int]((Get-Date) - $t0).TotalSeconds
|
||||
Write-Host "UAT code=$code duree=${dur}s journal=$log"
|
||||
if ($code -ne 0) { exit $code }
|
||||
|
||||
$win = Join-Path $ArchiveDir 'Windows'
|
||||
$exe = Join-Path $win 'LifePlay.exe'
|
||||
if (-not (Test-Path $exe)) {
|
||||
$win = Join-Path $ArchiveDir 'WindowsNoEditor'
|
||||
$exe = Join-Path $win 'LifePlay.exe'
|
||||
}
|
||||
if (-not (Test-Path $exe)) {
|
||||
Write-Error "LifePlay.exe introuvable sous $ArchiveDir"
|
||||
}
|
||||
|
||||
$batSrc = Join-Path $PSScriptRoot 'debug'
|
||||
Copy-Item (Join-Path $batSrc '*.bat') $win -Force
|
||||
Copy-Item (Join-Path $batSrc 'LifePlay_debug_ui.ps1') $win -Force
|
||||
$staged = @(Get-ChildItem $win -Filter 'LifePlay_debug_*.bat')
|
||||
Write-Host ("bats staged={0} -> {1}" -f $staged.Count, $win)
|
||||
|
||||
$bin = Join-Path $win 'LifePlay\Binaries\Win64'
|
||||
New-Item -ItemType Directory -Force $bin | Out-Null
|
||||
$pdbSrc = Join-Path $ProjectRoot 'Binaries\Win64'
|
||||
$copied = 0
|
||||
if (Test-Path $pdbSrc) {
|
||||
Get-ChildItem $pdbSrc -Filter "*Win64-$Config.pdb" | ForEach-Object {
|
||||
Copy-Item $_.FullName $bin -Force
|
||||
$copied++
|
||||
}
|
||||
}
|
||||
Write-Host "pdb copies=$copied -> $bin"
|
||||
Write-Host "archive=$ArchiveDir exe=$exe"
|
||||
exit 0
|
||||
100
Tools/CollectDebugArtifacts.ps1
Normal file
100
Tools/CollectDebugArtifacts.ps1
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Archive les preuves d'une session debug (journal, CSV, LLM, utrace, MemReport, CVD).
|
||||
|
||||
.EXAMPLE
|
||||
.\Tools\CollectDebugArtifacts.ps1 -Lot Dbg-I4 -BuildDir D:\Game\LifePlay_Build_5.5_DebugGame -Session A
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$Lot,
|
||||
[Parameter(Mandatory)][string]$BuildDir,
|
||||
[Parameter(Mandatory)][ValidateSet('A', 'B', 'C', 'bugs', 'gpu', 'collision')][string]$Session,
|
||||
[string]$BackupRoot = 'D:\Game\LifePlay_Backup_5.5'
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Resolve-SavedDir([string]$Root) {
|
||||
foreach ($rel in @(
|
||||
'Windows\LifePlay\Saved',
|
||||
'WindowsNoEditor\LifePlay\Saved',
|
||||
'LifePlay\Saved',
|
||||
'Saved')) {
|
||||
$p = Join-Path $Root $rel
|
||||
if (Test-Path $p) { return $p }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
$saved = Resolve-SavedDir $BuildDir
|
||||
if (-not $saved) {
|
||||
Write-Error "Saved introuvable sous $BuildDir"
|
||||
}
|
||||
|
||||
$dest = Join-Path $BackupRoot "$Lot\Session$Session"
|
||||
New-Item -ItemType Directory -Force $dest | Out-Null
|
||||
|
||||
$copied = New-Object System.Collections.Generic.List[string]
|
||||
|
||||
function Copy-Match([string]$RelativeDir, [string]$Filter) {
|
||||
$from = Join-Path $saved $RelativeDir
|
||||
if (-not (Test-Path $from)) { return }
|
||||
$target = Join-Path $dest $RelativeDir
|
||||
New-Item -ItemType Directory -Force $target | Out-Null
|
||||
Get-ChildItem $from -Filter $Filter -File -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
Copy-Item $_.FullName (Join-Path $target $_.Name) -Force
|
||||
$script:copied.Add((Join-Path $RelativeDir $_.Name))
|
||||
}
|
||||
}
|
||||
|
||||
Copy-Match 'Logs' '*.log'
|
||||
Copy-Match 'Profiling' '*.utrace'
|
||||
Copy-Match 'Profiling\CSV' '*.csv'
|
||||
Copy-Match 'Profiling\LLM' '*.csv'
|
||||
Copy-Match 'Profiling\LPDebug' '*.txt'
|
||||
|
||||
$reportRoot = Join-Path $saved 'Profiling\MemReports'
|
||||
if (Test-Path $reportRoot) {
|
||||
Get-ChildItem $reportRoot -Recurse -Filter '*.memreport' -File | ForEach-Object {
|
||||
$rel = $_.FullName.Substring($saved.Length).TrimStart('\')
|
||||
$target = Join-Path $dest $rel
|
||||
New-Item -ItemType Directory -Force (Split-Path $target -Parent) | Out-Null
|
||||
Copy-Item $_.FullName $target -Force
|
||||
$copied.Add($rel)
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($dirName in @('ChaosVisualDebugger', 'ChaosVD')) {
|
||||
$from = Join-Path $saved $dirName
|
||||
if (Test-Path $from) {
|
||||
$target = Join-Path $dest $dirName
|
||||
Copy-Item $from $target -Recurse -Force
|
||||
$copied.Add($dirName)
|
||||
}
|
||||
}
|
||||
|
||||
Get-ChildItem $saved -Recurse -Include '*.utrace', '*.cvd' -File -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
$rel = $_.FullName.Substring($saved.Length).TrimStart('\')
|
||||
if ($copied -contains $rel) { return }
|
||||
$target = Join-Path $dest $rel
|
||||
New-Item -ItemType Directory -Force (Split-Path $target -Parent) | Out-Null
|
||||
Copy-Item $_.FullName $target -Force
|
||||
$copied.Add($rel)
|
||||
}
|
||||
|
||||
$manifest = Join-Path $dest 'manifest.txt'
|
||||
@(
|
||||
"Lot=$Lot"
|
||||
"Session=$Session"
|
||||
"BuildDir=$BuildDir"
|
||||
"Saved=$saved"
|
||||
"Date=$(Get-Date -Format s)"
|
||||
"Files=$($copied.Count)"
|
||||
''
|
||||
$copied
|
||||
) | Set-Content $manifest -Encoding UTF8
|
||||
|
||||
Write-Host "dest=$dest fichiers=$($copied.Count)"
|
||||
if ($copied.Count -lt 1) {
|
||||
Write-Error "Aucun artefact copie depuis $saved"
|
||||
}
|
||||
exit 0
|
||||
63
Tools/SummarizeDebugCsv.ps1
Normal file
63
Tools/SummarizeDebugCsv.ps1
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Mediane FrameTime et decompte des images longues d'un CSV du profileur (session C).
|
||||
|
||||
.EXAMPLE
|
||||
.\Tools\SummarizeDebugCsv.ps1 -Csv D:\Game\LifePlay_Backup_5.5\Dbg-I6\SessionC\Profiling\CSV
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$Csv
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$files = @()
|
||||
if (Test-Path $Csv -PathType Container) {
|
||||
$files = @(Get-ChildItem $Csv -Filter '*.csv' -File)
|
||||
} elseif (Test-Path $Csv) {
|
||||
$files = @(Get-Item $Csv)
|
||||
}
|
||||
if ($files.Count -lt 1) { Write-Error "CSV introuvable : $Csv" }
|
||||
|
||||
function Median([double[]]$Values) {
|
||||
$s = @($Values | Sort-Object)
|
||||
if ($s.Count -eq 0) { return [double]::NaN }
|
||||
$mid = [int][math]::Floor($s.Count / 2)
|
||||
if ($s.Count % 2) { return [double]$s[$mid] }
|
||||
return ([double]$s[$mid - 1] + [double]$s[$mid]) / 2.0
|
||||
}
|
||||
|
||||
$frame = New-Object System.Collections.Generic.List[double]
|
||||
$game = New-Object System.Collections.Generic.List[double]
|
||||
$over100 = 0
|
||||
$over50 = 0
|
||||
$physicsMb = $null
|
||||
|
||||
foreach ($file in $files) {
|
||||
$rows = Import-Csv $file.FullName
|
||||
$hasFrame = $rows.Count -gt 0 -and ($rows[0].PSObject.Properties.Name -contains 'FrameTime')
|
||||
if (-not $hasFrame) { continue }
|
||||
foreach ($row in $rows) {
|
||||
$ft = 0.0
|
||||
if (-not [double]::TryParse($row.FrameTime, [Globalization.NumberStyles]::Float, [Globalization.CultureInfo]::InvariantCulture, [ref]$ft)) { continue }
|
||||
$frame.Add($ft)
|
||||
if ($ft -gt 100) { $over100++ }
|
||||
if ($ft -gt 50) { $over50++ }
|
||||
if ($row.PSObject.Properties.Name -contains 'GameThreadTime') {
|
||||
$gt = 0.0
|
||||
if ([double]::TryParse($row.GameThreadTime, [Globalization.NumberStyles]::Float, [Globalization.CultureInfo]::InvariantCulture, [ref]$gt)) { $game.Add($gt) }
|
||||
}
|
||||
}
|
||||
if ($rows.Count -gt 0 -and $rows[0].PSObject.Properties.Name -contains 'LLM/Physics/Chaos') {
|
||||
$last = $rows[$rows.Count - 1].'LLM/Physics/Chaos'
|
||||
$bytes = 0.0
|
||||
if ([double]::TryParse($last, [Globalization.NumberStyles]::Float, [Globalization.CultureInfo]::InvariantCulture, [ref]$bytes)) { $physicsMb = [math]::Round($bytes / 1MB, 1) }
|
||||
}
|
||||
}
|
||||
|
||||
if ($frame.Count -lt 1) { Write-Error "Colonne FrameTime absente dans $Csv" }
|
||||
|
||||
$med = Median $frame.ToArray()
|
||||
$medGame = Median $game.ToArray()
|
||||
Write-Host ("Fichiers={0} Images={1} MedianeFrameTimeMs={2:N2} MedianeGameThreadMs={3:N2} ImagesGt50ms={4} ImagesGt100ms={5} PhysicsChaosMB={6}" -f `
|
||||
$files.Count, $frame.Count, $med, $medGame, $over50, $over100, $physicsMb)
|
||||
exit 0
|
||||
4
Tools/debug/LifePlay_debug_bugs.bat
Normal file
4
Tools/debug/LifePlay_debug_bugs.bat
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
@echo off
|
||||
rem Auxiliaire bugs. Inactif par defaut : -LPDump* et -LPTrace* (voir 09_Commandes_Utiles.md).
|
||||
setlocal
|
||||
start "" "%~dp0LifePlay.exe" -LogCmds="LogModdingExtra Verbose"
|
||||
5
Tools/debug/LifePlay_debug_collision.bat
Normal file
5
Tools/debug/LifePlay_debug_collision.bat
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
@echo off
|
||||
rem Auxiliaire collision. -LPCollisionLog echantillonne apres le chargement
|
||||
rem (pas de LifePlay.CollisionDump au demarrage : il partait avant la ville).
|
||||
setlocal
|
||||
start "" "%~dp0LifePlay.exe" -LPCollisionLog -ExecCmds="p.Chaos.StartVDRecording, show collision"
|
||||
5
Tools/debug/LifePlay_debug_gpu.bat
Normal file
5
Tools/debug/LifePlay_debug_gpu.bat
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
@echo off
|
||||
rem Auxiliaire GPU : ce lanceur prepare PIX (D3D12).
|
||||
rem Radeon RGP : lancer LifePlay.exe sans ce fichier et attacher le processus. Jamais PIX et RGP ensemble.
|
||||
setlocal
|
||||
start "" "%~dp0LifePlay.exe" -d3d12
|
||||
7
Tools/debug/LifePlay_debug_sessionA_insights.bat
Normal file
7
Tools/debug/LifePlay_debug_sessionA_insights.bat
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
@echo off
|
||||
rem Session A — Insights (build DebugGame). Scenario O0, environ 15 min.
|
||||
rem Les millisecondes absolues ne se comparent pas a une build Development.
|
||||
setlocal
|
||||
set "SAVED=%~dp0LifePlay\Saved\Profiling"
|
||||
if not exist "%SAVED%" mkdir "%SAVED%"
|
||||
start "" "%~dp0LifePlay.exe" -trace=cpu,frame,bookmark,loadtime,memory -statnamedevents -tracefile="%SAVED%\SessionA.utrace" -LPHitchLog
|
||||
6
Tools/debug/LifePlay_debug_sessionB_mem.bat
Normal file
6
Tools/debug/LifePlay_debug_sessionB_mem.bat
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
@echo off
|
||||
rem Session B — memoire et collisions (build DebugGame).
|
||||
rem ChaosVD : p.Chaos.StartVDRecording. Si la commande est absente (plugin editeur seul),
|
||||
rem LifePlay.CollisionDump reste la preuve. Ne pas combiner PIX et RGP.
|
||||
setlocal
|
||||
start "" "%~dp0LifePlay.exe" -llm -llmcsv -LPCollisionLog -ExecCmds="LifePlay.MemSnapshot, p.Chaos.StartVDRecording"
|
||||
5
Tools/debug/LifePlay_debug_sessionC_profil.bat
Normal file
5
Tools/debug/LifePlay_debug_sessionC_profil.bat
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
@echo off
|
||||
rem Session C — non-regression CSV (build Development seulement). Sans -LPHitchLog.
|
||||
rem Jamais -csvCaptureFrames : la capture demarre avant le RHI et le moteur s'arrete.
|
||||
setlocal
|
||||
start "" "%~dp0LifePlay.exe" -csvGpuStats -llm -llmcsv -ExecCmds="csvprofile repeat=20, csvprofile frames=9000"
|
||||
4
Tools/debug/LifePlay_debug_ui.bat
Normal file
4
Tools/debug/LifePlay_debug_ui.bat
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
@echo off
|
||||
rem Fenetre de choix des options de mesure. Le script .ps1 doit rester a cote de ce .bat.
|
||||
setlocal
|
||||
powershell -NoProfile -STA -ExecutionPolicy Bypass -File "%~dp0LifePlay_debug_ui.ps1"
|
||||
275
Tools/debug/LifePlay_debug_ui.ps1
Normal file
275
Tools/debug/LifePlay_debug_ui.ps1
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
# Lanceur graphique des options de mesure (Docs/Migration/18_Build_Debug.md).
|
||||
# Double-clic : LifePlay_debug_ui.bat. Ne propose jamais -csvCaptureFrames.
|
||||
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Find-GameExes {
|
||||
$here = Split-Path -Parent $PSCommandPath
|
||||
$list = New-Object System.Collections.Generic.List[string]
|
||||
$local = Join-Path $here 'LifePlay.exe'
|
||||
if (Test-Path -LiteralPath $local) { $list.Add($local) }
|
||||
foreach ($p in @(
|
||||
'D:\Game\LifePlay_Build_5.5_Development\Windows\LifePlay.exe',
|
||||
'D:\Game\LifePlay_Build_5.5_DebugGame\Windows\LifePlay.exe')) {
|
||||
if ((Test-Path -LiteralPath $p) -and -not $list.Contains($p)) { $list.Add($p) }
|
||||
}
|
||||
return $list
|
||||
}
|
||||
|
||||
$script:Applying = $false
|
||||
|
||||
$form = New-Object System.Windows.Forms.Form
|
||||
$form.Text = 'LifePlay - options de mesure'
|
||||
$form.StartPosition = 'CenterScreen'
|
||||
$form.FormBorderStyle = 'FixedDialog'
|
||||
$form.MaximizeBox = $false
|
||||
$form.ClientSize = New-Object System.Drawing.Size(560, 640)
|
||||
$form.Font = New-Object System.Drawing.Font('Segoe UI', 9)
|
||||
|
||||
$y = 12
|
||||
$lblGame = New-Object System.Windows.Forms.Label
|
||||
$lblGame.Text = 'Jeu'
|
||||
$lblGame.Location = New-Object System.Drawing.Point(16, $y)
|
||||
$lblGame.AutoSize = $true
|
||||
$form.Controls.Add($lblGame)
|
||||
|
||||
$cmbExe = New-Object System.Windows.Forms.ComboBox
|
||||
$cmbExe.DropDownStyle = 'DropDownList'
|
||||
$cmbExe.Location = New-Object System.Drawing.Point(16, 32)
|
||||
$cmbExe.Width = 420
|
||||
$form.Controls.Add($cmbExe)
|
||||
|
||||
$btnBrowse = New-Object System.Windows.Forms.Button
|
||||
$btnBrowse.Text = 'Parcourir'
|
||||
$btnBrowse.Location = New-Object System.Drawing.Point(444, 30)
|
||||
$btnBrowse.Width = 100
|
||||
$form.Controls.Add($btnBrowse)
|
||||
|
||||
$lblPreset = New-Object System.Windows.Forms.Label
|
||||
$lblPreset.Text = 'Reglage'
|
||||
$lblPreset.Location = New-Object System.Drawing.Point(16, 68)
|
||||
$lblPreset.AutoSize = $true
|
||||
$form.Controls.Add($lblPreset)
|
||||
|
||||
$cmbPreset = New-Object System.Windows.Forms.ComboBox
|
||||
$cmbPreset.DropDownStyle = 'DropDownList'
|
||||
$cmbPreset.Location = New-Object System.Drawing.Point(16, 88)
|
||||
$cmbPreset.Width = 528
|
||||
[void]$cmbPreset.Items.AddRange(@(
|
||||
'Personnalise',
|
||||
'Collisions',
|
||||
'Memoire et physique (session B)',
|
||||
'CSV 9000 images (session C)',
|
||||
'Insights court, sans memory',
|
||||
'Journal d''a-coups'
|
||||
))
|
||||
$cmbPreset.SelectedIndex = 1
|
||||
$form.Controls.Add($cmbPreset)
|
||||
|
||||
function New-Check([string]$Text, [int]$Top) {
|
||||
$c = New-Object System.Windows.Forms.CheckBox
|
||||
$c.Text = $Text
|
||||
$c.Location = New-Object System.Drawing.Point(16, $Top)
|
||||
$c.AutoSize = $true
|
||||
$c.Width = 520
|
||||
return $c
|
||||
}
|
||||
|
||||
$chkHitch = New-Check 'Journal d''a-coups (-LPHitchLog)' 128
|
||||
$chkCol = New-Check 'Suivi des collisions (-LPCollisionLog), apres le chargement' 156
|
||||
$chkCvd = New-Check 'Enregistrement ChaosVD (p.Chaos.StartVDRecording)' 184
|
||||
$chkShow = New-Check 'Afficher les collisions (show collision)' 212
|
||||
$chkSnap = New-Check 'Instantane memoire au demarrage (LifePlay.MemSnapshot)' 240
|
||||
$chkLlm = New-Check 'Memoire LLM (-llm -llmcsv)' 268
|
||||
$chkCsv = New-Check 'CSV 9000 images, demarre apres le rendu (pas -csvCaptureFrames)' 296
|
||||
$chkTrace = New-Check 'Trace Insights : cpu, frame, bookmark, loadtime' 324
|
||||
$chkMemCh = New-Check 'Canal memory (plusieurs Go, Insights sature au-dela)' 352
|
||||
$chkVerbose = New-Check 'Journal des mods verbeux (LogModdingExtra)' 380
|
||||
$chkPix = New-Check 'PIX / D3D12 (-d3d12). Ne pas attacher RGP en meme temps.' 408
|
||||
foreach ($c in @($chkHitch, $chkCol, $chkCvd, $chkShow, $chkSnap, $chkLlm, $chkCsv, $chkTrace, $chkMemCh, $chkVerbose, $chkPix)) {
|
||||
$form.Controls.Add($c)
|
||||
}
|
||||
|
||||
$lblMs = New-Object System.Windows.Forms.Label
|
||||
$lblMs.Text = 'Seuil (ms)'
|
||||
$lblMs.Location = New-Object System.Drawing.Point(300, 130)
|
||||
$lblMs.AutoSize = $true
|
||||
$form.Controls.Add($lblMs)
|
||||
$txtMs = New-Object System.Windows.Forms.TextBox
|
||||
$txtMs.Text = '50'
|
||||
$txtMs.Location = New-Object System.Drawing.Point(370, 126)
|
||||
$txtMs.Width = 48
|
||||
$form.Controls.Add($txtMs)
|
||||
|
||||
$txtCmd = New-Object System.Windows.Forms.TextBox
|
||||
$txtCmd.Location = New-Object System.Drawing.Point(16, 448)
|
||||
$txtCmd.Size = New-Object System.Drawing.Size(528, 110)
|
||||
$txtCmd.Multiline = $true
|
||||
$txtCmd.ReadOnly = $true
|
||||
$txtCmd.ScrollBars = 'Vertical'
|
||||
$txtCmd.Font = New-Object System.Drawing.Font('Consolas', 8)
|
||||
$form.Controls.Add($txtCmd)
|
||||
|
||||
$lblWarn = New-Object System.Windows.Forms.Label
|
||||
$lblWarn.AutoSize = $false
|
||||
$lblWarn.Location = New-Object System.Drawing.Point(16, 564)
|
||||
$lblWarn.Size = New-Object System.Drawing.Size(360, 36)
|
||||
$form.Controls.Add($lblWarn)
|
||||
|
||||
$btnLaunch = New-Object System.Windows.Forms.Button
|
||||
$btnLaunch.Text = 'Lancer'
|
||||
$btnLaunch.Location = New-Object System.Drawing.Point(388, 592)
|
||||
$btnLaunch.Width = 156
|
||||
$btnLaunch.Height = 32
|
||||
$form.Controls.Add($btnLaunch)
|
||||
$form.AcceptButton = $btnLaunch
|
||||
|
||||
function Get-SelectedExe {
|
||||
if ($cmbExe.SelectedItem) { return [string]$cmbExe.SelectedItem }
|
||||
return ''
|
||||
}
|
||||
|
||||
function Build-Args {
|
||||
$exe = Get-SelectedExe
|
||||
$parts = New-Object System.Collections.Generic.List[string]
|
||||
if ($chkHitch.Checked) {
|
||||
$parts.Add('-LPHitchLog')
|
||||
$ms = 50
|
||||
[void][int]::TryParse($txtMs.Text.Trim(), [ref]$ms)
|
||||
if ($ms -gt 0 -and $ms -ne 50) { $parts.Add("-LPHitchMs=$ms") }
|
||||
}
|
||||
if ($chkCol.Checked) { $parts.Add('-LPCollisionLog') }
|
||||
if ($chkLlm.Checked) { $parts.Add('-llm'); $parts.Add('-llmcsv') }
|
||||
if ($chkCsv.Checked) { $parts.Add('-csvGpuStats') }
|
||||
if ($chkTrace.Checked -and $exe) {
|
||||
$channels = 'cpu,frame,bookmark,loadtime'
|
||||
if ($chkMemCh.Checked) { $channels += ',memory' }
|
||||
$parts.Add("-trace=$channels")
|
||||
$parts.Add('-statnamedevents')
|
||||
$saved = Join-Path (Split-Path -Parent $exe) 'LifePlay\Saved\Profiling'
|
||||
$file = Join-Path $saved ('UI_{0:yyyyMMdd_HHmmss}.utrace' -f (Get-Date))
|
||||
$parts.Add("-tracefile=`"$file`"")
|
||||
}
|
||||
if ($chkPix.Checked) { $parts.Add('-d3d12') }
|
||||
if ($chkVerbose.Checked) { $parts.Add('-LogCmds="LogModdingExtra Verbose"') }
|
||||
|
||||
$exec = New-Object System.Collections.Generic.List[string]
|
||||
if ($chkCsv.Checked) {
|
||||
$exec.Add('csvprofile repeat=20')
|
||||
$exec.Add('csvprofile frames=9000')
|
||||
}
|
||||
if ($chkSnap.Checked) { $exec.Add('LifePlay.MemSnapshot') }
|
||||
if ($chkCvd.Checked) { $exec.Add('p.Chaos.StartVDRecording') }
|
||||
if ($chkShow.Checked) { $exec.Add('show collision') }
|
||||
if ($exec.Count -gt 0) { $parts.Add('-ExecCmds="' + ($exec -join ', ') + '"') }
|
||||
return $parts
|
||||
}
|
||||
|
||||
function Update-Preview {
|
||||
$chkMemCh.Enabled = $chkTrace.Checked
|
||||
$txtMs.Enabled = $chkHitch.Checked
|
||||
if (-not $chkTrace.Checked) { $chkMemCh.Checked = $false }
|
||||
$exe = Get-SelectedExe
|
||||
$parts = Build-Args
|
||||
if (-not $exe) {
|
||||
$txtCmd.Text = 'LifePlay.exe introuvable.'
|
||||
$btnLaunch.Enabled = $false
|
||||
}
|
||||
else {
|
||||
$txtCmd.Text = "`"$exe`"`r`n" + ($parts -join "`r`n")
|
||||
$btnLaunch.Enabled = $true
|
||||
}
|
||||
$notes = New-Object System.Collections.Generic.List[string]
|
||||
if ($chkCsv.Checked -and $chkHitch.Checked) {
|
||||
$notes.Add('La session CSV de non-regression se joue sans journal d''a-coups.')
|
||||
}
|
||||
if ($chkMemCh.Checked) {
|
||||
$notes.Add('Le canal memory a deja produit une trace de 6,4 Go.')
|
||||
}
|
||||
if ($exe -match 'DebugGame' -and $chkCsv.Checked) {
|
||||
$notes.Add('Les ms DebugGame ne se comparent pas a Development.')
|
||||
}
|
||||
$lblWarn.Text = ($notes -join ' ')
|
||||
}
|
||||
|
||||
function Apply-Preset([string]$Name) {
|
||||
$map = @{
|
||||
'Collisions' = @{ H = $false; C = $true; V = $true; S = $true; M = $false; L = $false; P = $false; T = $false; Mem = $false; Verb = $false; Pix = $false }
|
||||
'Memoire et physique (session B)' = @{ H = $false; C = $true; V = $true; S = $false; M = $true; L = $true; P = $false; T = $false; Mem = $false; Verb = $false; Pix = $false }
|
||||
'CSV 9000 images (session C)' = @{ H = $false; C = $false; V = $false; S = $false; M = $false; L = $true; P = $true; T = $false; Mem = $false; Verb = $false; Pix = $false }
|
||||
'Insights court, sans memory' = @{ H = $true; C = $false; V = $false; S = $false; M = $false; L = $false; P = $false; T = $true; Mem = $false; Verb = $false; Pix = $false }
|
||||
'Journal d''a-coups' = @{ H = $true; C = $false; V = $false; S = $false; M = $false; L = $false; P = $false; T = $false; Mem = $false; Verb = $false; Pix = $false }
|
||||
}
|
||||
if (-not $map.ContainsKey($Name)) { return }
|
||||
$p = $map[$Name]
|
||||
$script:Applying = $true
|
||||
$chkHitch.Checked = $p.H
|
||||
$chkCol.Checked = $p.C
|
||||
$chkCvd.Checked = $p.V
|
||||
$chkShow.Checked = $p.S
|
||||
$chkSnap.Checked = $p.M
|
||||
$chkLlm.Checked = $p.L
|
||||
$chkCsv.Checked = $p.P
|
||||
$chkTrace.Checked = $p.T
|
||||
$chkMemCh.Checked = $p.Mem
|
||||
$chkVerbose.Checked = $p.Verb
|
||||
$chkPix.Checked = $p.Pix
|
||||
$script:Applying = $false
|
||||
Update-Preview
|
||||
}
|
||||
|
||||
function Mark-Custom {
|
||||
if ($script:Applying) { return }
|
||||
$script:Applying = $true
|
||||
$cmbPreset.SelectedItem = 'Personnalise'
|
||||
$script:Applying = $false
|
||||
Update-Preview
|
||||
}
|
||||
|
||||
foreach ($exe in (Find-GameExes)) { [void]$cmbExe.Items.Add($exe) }
|
||||
if ($cmbExe.Items.Count -gt 0) { $cmbExe.SelectedIndex = 0 }
|
||||
|
||||
$cmbExe.Add_SelectedIndexChanged({ Update-Preview })
|
||||
$cmbPreset.Add_SelectedIndexChanged({
|
||||
if ($script:Applying) { return }
|
||||
Apply-Preset ([string]$cmbPreset.SelectedItem)
|
||||
})
|
||||
foreach ($c in @($chkHitch, $chkCol, $chkCvd, $chkShow, $chkSnap, $chkLlm, $chkCsv, $chkTrace, $chkMemCh, $chkVerbose, $chkPix)) {
|
||||
$c.Add_CheckedChanged({ Mark-Custom })
|
||||
}
|
||||
$txtMs.Add_TextChanged({ Update-Preview })
|
||||
|
||||
$btnBrowse.Add_Click({
|
||||
$dlg = New-Object System.Windows.Forms.OpenFileDialog
|
||||
$dlg.Filter = 'LifePlay.exe|LifePlay.exe'
|
||||
$dlg.Title = 'Choisir LifePlay.exe'
|
||||
if ($dlg.ShowDialog() -eq 'OK') {
|
||||
if (-not $cmbExe.Items.Contains($dlg.FileName)) { [void]$cmbExe.Items.Add($dlg.FileName) }
|
||||
$cmbExe.SelectedItem = $dlg.FileName
|
||||
}
|
||||
})
|
||||
|
||||
$btnLaunch.Add_Click({
|
||||
$exe = Get-SelectedExe
|
||||
if (-not $exe -or -not (Test-Path -LiteralPath $exe)) {
|
||||
[System.Windows.Forms.MessageBox]::Show('LifePlay.exe introuvable.', 'LifePlay')
|
||||
return
|
||||
}
|
||||
if ($chkTrace.Checked) {
|
||||
$saved = Join-Path (Split-Path -Parent $exe) 'LifePlay\Saved\Profiling'
|
||||
New-Item -ItemType Directory -Force -Path $saved | Out-Null
|
||||
}
|
||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$psi.FileName = $exe
|
||||
$psi.WorkingDirectory = Split-Path -Parent $exe
|
||||
$psi.UseShellExecute = $true
|
||||
$psi.Arguments = (Build-Args) -join ' '
|
||||
[void][System.Diagnostics.Process]::Start($psi)
|
||||
$form.Close()
|
||||
})
|
||||
|
||||
Apply-Preset 'Collisions'
|
||||
[void]$form.ShowDialog()
|
||||
Loading…
Reference in a new issue